diff --git a/.gitattributes b/.gitattributes index 1870c80..c6e5aec 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ # Disable any line endings auto conversion. -* -text \ No newline at end of file +* -text +*.zip filter=lfs diff=lfs merge=lfs -text diff --git a/packages/P4DEnvironment.dpk b/packages/P4DEnvironment.dpk index 3199fe4..ac2abdd 100644 --- a/packages/P4DEnvironment.dpk +++ b/packages/P4DEnvironment.dpk @@ -41,7 +41,6 @@ contains PyEnvironment.Local in '..\src\PyEnvironment.Local.pas', PyEnvironment.Embeddable in '..\src\Embeddable\PyEnvironment.Embeddable.pas', PyEnvironment.Embeddable.Res in '..\src\Embeddable\Res\PyEnvironment.Embeddable.Res.pas', - PyEnvironment.Embeddable.Res.Python37 in '..\src\Embeddable\Res\PyEnvironment.Embeddable.Res.Python37.pas', PyEnvironment.Embeddable.Res.Python38 in '..\src\Embeddable\Res\PyEnvironment.Embeddable.Res.Python38.pas', PyEnvironment.Embeddable.Res.Python39 in '..\src\Embeddable\Res\PyEnvironment.Embeddable.Res.Python39.pas', PyEnvironment.Embeddable.Res.Python310 in '..\src\Embeddable\Res\PyEnvironment.Embeddable.Res.Python310.pas', diff --git a/packages/P4DEnvironment.dproj b/packages/P4DEnvironment.dproj index 838fcb5..c71b454 100644 --- a/packages/P4DEnvironment.dproj +++ b/packages/P4DEnvironment.dproj @@ -8,7 +8,7 @@ Win32 {4A75F3AF-14CB-4FCD-8871-BD7A6A516746} 19.5 - 168083 + 693395 true @@ -133,11 +133,11 @@ Debug - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface Debug - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface Debug @@ -167,7 +167,7 @@ /usr/X11/bin/xterm -e "%debuggee%" - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface false @@ -209,7 +209,6 @@ - @@ -221,24 +220,6 @@
getpipscript.res
- -
python3-android-3.7-arm.res
-
- -
python3-android-3.7-arm64.res
-
- -
python3-linux-3.7-x86_64.res
-
- -
python3-macos-3.7-x86_64.res
-
- -
python3-windows-3.7-amd64.res
-
- -
python3-windows-3.7-win32.res
-
python3-android-3.8-arm.res
@@ -351,8 +332,8 @@ True True - False - False + True + True True True True diff --git a/packages/P4DPythonEnvironmentsComponentSuite.prjmgc b/packages/P4DPythonEnvironmentsComponentSuite.prjmgc deleted file mode 100644 index e1cd399..0000000 --- a/packages/P4DPythonEnvironmentsComponentSuite.prjmgc +++ /dev/null @@ -1,14 +0,0 @@ -[Settings] -AutoLibSuffix=0 -ClearChildAppSettings=0 -ClearChildPackageSettings=0 -ClearChildVersionInfo=0 -NormalizeDproj=1 -SplitDproj=0 -EnableMissingPlatforms=1 -RemoveUnusedPlatforms=1 -RefreshFormType=0 -RemoveExcludedPackages=1 -RemoveDeployment=1 -RemoveUWP=1 - diff --git a/packages/P4DTools.dproj b/packages/P4DTools.dproj index e789890..7541826 100644 --- a/packages/P4DTools.dproj +++ b/packages/P4DTools.dproj @@ -8,7 +8,7 @@ Win32 {E39CABA2-57A4-49D6-AA1E-35E9A11063F1} 19.5 - 168083 + 693395
true @@ -82,6 +82,12 @@ true true + + true + Cfg_2 + true + true + true Cfg_2 @@ -91,7 +97,6 @@ P4DTools ..\resources - All ..\lib\$(Platform)\$(Config) .\$(Platform)\$(Config) System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) @@ -122,12 +127,12 @@ Debug /usr/X11/bin/xterm -e "%debuggee%" - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface Debug /usr/X11/bin/xterm -e "%debuggee%" - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface Debug @@ -174,6 +179,9 @@ 1 package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= + + Debug + true 1033 @@ -225,8 +233,8 @@ True True - False - False + True + True True True True diff --git a/packages/dclP4DEnvironmentProject.dpk b/packages/dclP4DEnvironmentProject.dpk index 749bdc2..1a6980d 100644 --- a/packages/dclP4DEnvironmentProject.dpk +++ b/packages/dclP4DEnvironmentProject.dpk @@ -38,9 +38,19 @@ contains PyEnvironment.Project.IDE.Helper in '..\src\Project\IDE\PyEnvironment.Project.IDE.Helper.pas', PyEnvironment.Project.IDE.ManagerMenu in '..\src\Project\IDE\PyEnvironment.Project.IDE.ManagerMenu.pas', PyEnvironment.Project.IDE.Menu in '..\src\Project\IDE\PyEnvironment.Project.IDE.Menu.pas', - PyEnvironment.Project.IDE.Deploy in '..\src\Project\IDE\PyEnvironment.Project.IDE.Deploy.pas', + PyEnvironment.Project.IDE.Deploy in '..\src\Project\IDE\Deploy\PyEnvironment.Project.IDE.Deploy.pas', PyEnvironment.Project.IDE.Registration in '..\src\Project\IDE\PyEnvironment.Project.IDE.Registration.pas', - PyEnvironment.Project.IDE.Types in '..\src\Project\IDE\PyEnvironment.Project.IDE.Types.pas'; + PyEnvironment.Project.IDE.Types in '..\src\Project\IDE\PyEnvironment.Project.IDE.Types.pas', + PyEnvironment.Project.IDE.Deploy.iOS in '..\src\Project\IDE\Deploy\iOS\PyEnvironment.Project.IDE.Deploy.iOS.pas', + PyEnvironment.Project.IDE.Deploy.iOSSimARM64 in '..\src\Project\IDE\Deploy\iOS\PyEnvironment.Project.IDE.Deploy.iOSSimARM64.pas', + PyEnvironment.Project.IDE.Deploy.iOSDevice64 in '..\src\Project\IDE\Deploy\iOS\PyEnvironment.Project.IDE.Deploy.iOSDevice64.pas', + PyEnvironment.Project.IDE.Deploy.Platform in '..\src\Project\IDE\Deploy\PyEnvironment.Project.IDE.Deploy.Platform.pas', + PyEnvironment.Project.IDE.Deploy.OSX in '..\src\Project\IDE\Deploy\OSX\PyEnvironment.Project.IDE.Deploy.OSX.pas', + PyEnvironment.Project.IDE.Deploy.OSX64 in '..\src\Project\IDE\Deploy\OSX\PyEnvironment.Project.IDE.Deploy.OSX64.pas', + PyEnvironment.Project.IDE.Deploy.OSXARM64 in '..\src\Project\IDE\Deploy\OSX\PyEnvironment.Project.IDE.Deploy.OSXARM64.pas', + PyEnvironment.Project.IDE.Deploy.Android in '..\src\Project\IDE\Deploy\Android\PyEnvironment.Project.IDE.Deploy.Android.pas', + PyEnvironment.Project.IDE.Deploy.AndroidARM in '..\src\Project\IDE\Deploy\Android\PyEnvironment.Project.IDE.Deploy.AndroidARM.pas', + PyEnvironment.Project.IDE.Deploy.AndroidARM64 in '..\src\Project\IDE\Deploy\Android\PyEnvironment.Project.IDE.Deploy.AndroidARM64.pas'; end. diff --git a/packages/dclP4DEnvironmentProject.dproj b/packages/dclP4DEnvironmentProject.dproj index 91e303b..375fae9 100644 --- a/packages/dclP4DEnvironmentProject.dproj +++ b/packages/dclP4DEnvironmentProject.dproj @@ -113,9 +113,19 @@ $(PostBuildEvent)]]> - + + + + + + + + + + + Base diff --git a/python/README.md b/python/README.md index 18f96cc..6bbacb1 100644 --- a/python/README.md +++ b/python/README.md @@ -1,15 +1,11 @@ Python 3 embeddables for Windows, Linux, MacOS and Android ================ -These Python builds are provided by the [python3-embeddable](https://github.com/lmbelo/python3-embeddable) project. +These Python builds are provided by the [python3-embeddable](https://github.com/lmbelo/python3-embeddable) and [python3-apple-embeddable](https://github.com/lmbelo/python3-apple-embeddable) project. Notes ------------ -Android requires the executables to be deployed with the app bundle and must follow the lib__.so naming convention. -MacOS requires the executables to be deployed with the app bundle. - -Tips ------------- -1) Download the Python embeddables artifacts generated by the [CI](https://github.com/lmbelo/python3-embeddable/actions), then run the "extract_deliverables.py" script to extract the required files and create the folder structure; -2) Run the "provide_deliverables.py" script to create the Delphi code that generates the deployment automation. A new file called "deliverables_cmds.txt" will be created. Copy it into PyEnvironment.Project.IDE.Deploy.TPyEnvironmentProjectDeploy.Create; -3) Run the "generate_rc.py" script to generate the .rc files and create the folder structure. Copy it under resources. Remove all them from the Delphi project and add the new ones. \ No newline at end of file +Android requires the executables to be deployed with the app bundle and must follow the lib__.so naming convention. We send the Python launcher with a special name and create a symlink on bin. The same applies to the interpreter. +iOS requires executables to be deployed as independent frameworks. +Apple docs says to make frameworks for OSX as well, but the extension modules seems to work fine on lib-dynload. We only make framework for the Python interpreter. +Nothing special for Windows or Linux. \ No newline at end of file diff --git a/python/android/3.10.7/arm/libpython3.10.so b/python/android/3.10.7/arm/libpython3.10.so deleted file mode 100644 index 8b10ae4..0000000 Binary files a/python/android/3.10.7/arm/libpython3.10.so and /dev/null differ diff --git a/python/android/3.10.7/arm/libpythonlauncher3.10.so b/python/android/3.10.7/arm/libpythonlauncher3.10.so deleted file mode 100644 index b1729a9..0000000 Binary files a/python/android/3.10.7/arm/libpythonlauncher3.10.so and /dev/null differ diff --git a/python/android/3.10.7/arm64/libpython3.10.so b/python/android/3.10.7/arm64/libpython3.10.so deleted file mode 100644 index 4e699e2..0000000 Binary files a/python/android/3.10.7/arm64/libpython3.10.so and /dev/null differ diff --git a/python/android/3.10.7/arm64/libpythonlauncher3.10.so b/python/android/3.10.7/arm64/libpythonlauncher3.10.so deleted file mode 100644 index 9ebef2b..0000000 Binary files a/python/android/3.10.7/arm64/libpythonlauncher3.10.so and /dev/null differ diff --git a/python/android/3.11.2/arm/libpython3.11.so b/python/android/3.11.2/arm/libpython3.11.so deleted file mode 100644 index 8bc414a..0000000 Binary files a/python/android/3.11.2/arm/libpython3.11.so and /dev/null differ diff --git a/python/android/3.11.2/arm/libpythonlauncher3.11.so b/python/android/3.11.2/arm/libpythonlauncher3.11.so deleted file mode 100644 index 8fd5bdd..0000000 Binary files a/python/android/3.11.2/arm/libpythonlauncher3.11.so and /dev/null differ diff --git a/python/android/3.11.2/arm64/libpython3.11.so b/python/android/3.11.2/arm64/libpython3.11.so deleted file mode 100644 index 52973db..0000000 Binary files a/python/android/3.11.2/arm64/libpython3.11.so and /dev/null differ diff --git a/python/android/3.11.2/arm64/libpythonlauncher3.11.so b/python/android/3.11.2/arm64/libpythonlauncher3.11.so deleted file mode 100644 index 359c110..0000000 Binary files a/python/android/3.11.2/arm64/libpythonlauncher3.11.so and /dev/null differ diff --git a/python/android/3.7.16/arm/libpython3.7m.so b/python/android/3.7.16/arm/libpython3.7m.so deleted file mode 100644 index 49c891d..0000000 Binary files a/python/android/3.7.16/arm/libpython3.7m.so and /dev/null differ diff --git a/python/android/3.7.16/arm/libpythonlauncher3.7m.so b/python/android/3.7.16/arm/libpythonlauncher3.7m.so deleted file mode 100644 index 0a2d6cf..0000000 Binary files a/python/android/3.7.16/arm/libpythonlauncher3.7m.so and /dev/null differ diff --git a/python/android/3.7.16/arm64/libpython3.7m.so b/python/android/3.7.16/arm64/libpython3.7m.so deleted file mode 100644 index 8c9e0c1..0000000 Binary files a/python/android/3.7.16/arm64/libpython3.7m.so and /dev/null differ diff --git a/python/android/3.7.16/arm64/libpythonlauncher3.7m.so b/python/android/3.7.16/arm64/libpythonlauncher3.7m.so deleted file mode 100644 index 47871f3..0000000 Binary files a/python/android/3.7.16/arm64/libpythonlauncher3.7m.so and /dev/null differ diff --git a/python/android/3.8.16/arm/libpython3.8.so b/python/android/3.8.16/arm/libpython3.8.so deleted file mode 100644 index ec92be2..0000000 Binary files a/python/android/3.8.16/arm/libpython3.8.so and /dev/null differ diff --git a/python/android/3.8.16/arm/libpythonlauncher3.8.so b/python/android/3.8.16/arm/libpythonlauncher3.8.so deleted file mode 100644 index 7a4abbc..0000000 Binary files a/python/android/3.8.16/arm/libpythonlauncher3.8.so and /dev/null differ diff --git a/python/android/3.8.16/arm64/libpython3.8.so b/python/android/3.8.16/arm64/libpython3.8.so deleted file mode 100644 index 7050749..0000000 Binary files a/python/android/3.8.16/arm64/libpython3.8.so and /dev/null differ diff --git a/python/android/3.8.16/arm64/libpythonlauncher3.8.so b/python/android/3.8.16/arm64/libpythonlauncher3.8.so deleted file mode 100644 index 3365686..0000000 Binary files a/python/android/3.8.16/arm64/libpythonlauncher3.8.so and /dev/null differ diff --git a/python/android/3.9.16/arm/libpython3.9.so b/python/android/3.9.16/arm/libpython3.9.so deleted file mode 100644 index dea43bf..0000000 Binary files a/python/android/3.9.16/arm/libpython3.9.so and /dev/null differ diff --git a/python/android/3.9.16/arm/libpythonlauncher3.9.so b/python/android/3.9.16/arm/libpythonlauncher3.9.so deleted file mode 100644 index 7c35d29..0000000 Binary files a/python/android/3.9.16/arm/libpythonlauncher3.9.so and /dev/null differ diff --git a/python/android/3.9.16/arm64/libpython3.9.so b/python/android/3.9.16/arm64/libpython3.9.so deleted file mode 100644 index 203c328..0000000 Binary files a/python/android/3.9.16/arm64/libpython3.9.so and /dev/null differ diff --git a/python/android/3.9.16/arm64/libpythonlauncher3.9.so b/python/android/3.9.16/arm64/libpythonlauncher3.9.so deleted file mode 100644 index 20c236f..0000000 Binary files a/python/android/3.9.16/arm64/libpythonlauncher3.9.so and /dev/null differ diff --git a/python/extract_deliverables.py b/python/extract_deliverables.py deleted file mode 100644 index b2518ee..0000000 --- a/python/extract_deliverables.py +++ /dev/null @@ -1,88 +0,0 @@ -# 1) Create the android and macos folders if it doesn't exists -# 2) Scan for android embeddables -# 2.1) For each embeddable, get its version and arch, create a new folder and place the interpreter and launcher files -# 3) Scan for macos embeddables -# 3.1) For each embeddale, get its version and arch, create a new folder and place the interpreter and launcher files - -import os -import re -import shutil -from pathlib import Path -from zipfile import ZipFile - -# Delete the existing android and macos folders if they exists -def delete_folder(path, platform): - platform_path = os.path.join(path, platform) - if os.path.exists(platform_path): - shutil.rmtree(platform_path) - -# Extracts the required files from a given platform/arquitecture -# Creates its folder if needed -def extract_executables(path, file, platform, architecture): - # platform - platform_dir = os.path.join(path, platform) - os.makedirs(platform_dir, exist_ok=True) - # Get its version - version = re.search(f"python3-{platform}-([0-9].[0-9]([0-9])?.*)-{architecture}.zip", file) - if version: - found_ver = version.group(1) - ver_dir = os.path.join(platform_dir, found_ver) - os.makedirs(ver_dir, exist_ok=True) - - if platform == "macos": - if architecture == "x86_64": - architecture = "intel" - else: - architecture = "arm" - - arch_dir = os.path.join(ver_dir, architecture) - os.makedirs(arch_dir, exist_ok=True) - # Open zip file and get the interpreter and launcher files - with ZipFile(os.path.join(path, file)) as myzipfile: - #print(myzipfile.namelist()) - major_minor_nums = found_ver.split(".")[0:2] - ver = major_minor_nums[0] + "." + major_minor_nums[1] - if int(major_minor_nums[1]) <= 7: - ver += "m" - - ext = "so" - if platform == "macos": - ext = "dylib" - - with open(os.path.join(arch_dir, f"libpython{ver}.{ext}"), "wb") as target_file: - target_file.write(myzipfile.read(f"lib/libpython{ver}.{ext}")) - - if platform == "android": - with open(os.path.join(arch_dir, f"libpythonlauncher{ver}.{ext}"), "wb") as target_file: - target_file.write(myzipfile.read(f"bin/python{ver}")) - else: - with open(os.path.join(arch_dir, f"python{ver}"), "wb") as target_file: - target_file.write(myzipfile.read(f"bin/python{ver}")) - -cur_dir = os.path.realpath(os.path.dirname(__file__)) - -delete_folder(cur_dir, "android") -delete_folder(cur_dir, "macos") - -# android -arm_regex = re.compile('python3-android-[0-9].[0-9]([0-9])?.*-arm.zip') -arm64_regex = re.compile('python3-android-[0-9].[0-9]([0-9])?.*-arm64.zip') - -for root, dirs, files in os.walk(cur_dir): - for file in files: - if arm_regex.match(file): - extract_executables(cur_dir, file, "android", "arm") - elif arm64_regex.match(file): - extract_executables(cur_dir, file, "android", "arm64") - -# macOS -arm_regex = re.compile('python3-macos-[0-9].[0-9]([0-9])?.*-x86_64.zip') -arm64_regex = re.compile('python3-macos-[0-9].[0-9]([0-9])?.*-universal2.zip') - -for root, dirs, files in os.walk(cur_dir): - for file in files: - if arm_regex.match(file): - extract_executables(cur_dir, file, "macos", "x86_64") - elif arm64_regex.match(file): - extract_executables(cur_dir, file, "macos", "universal2") - diff --git a/python/generate_rc.py b/python/generate_rc.py deleted file mode 100644 index 97cdba4..0000000 --- a/python/generate_rc.py +++ /dev/null @@ -1,45 +0,0 @@ -import os -import shutil -import re - -# Delete the existing resources folder if exists -def rebuild_folder(path): - if os.path.exists(path): - shutil.rmtree(path) - os.makedirs(path) - -cur_dir = os.path.realpath(os.path.dirname(__file__)) -resources_dir = os.path.join(cur_dir, 'resources') - -# Sort all versions -ver_set = set([]) -for root, dirs, files in os.walk(cur_dir): - for file in files: - version = re.search(f"python3-.*-([0-9].[0-9]([0-9])?.*)-.*.zip", file) - if not version: - continue - found_ver = version.group(1) - major_minor_nums = found_ver.split(".")[0:2] - ver = major_minor_nums[0] + "." + major_minor_nums[1] - ver_set.add(ver) - -# Delete the old resources folder if it exists -rebuild_folder(resources_dir) -# Loop through files of a given version -for small_version in ver_set: - re_ver = re.compile(f'python3-.*-{small_version}.*-.*.zip') - for _, _, files in os.walk(cur_dir): - for file in files: - if re_ver.match(file): - platform = re.search(f"python3-(.*)-{small_version}", file).group(1) - architecture = re.search("(.*).zip", file[file.rindex('-')+1:]).group(1) - full_version = re.search(f"python3-{platform}-([0-9].[0-9]([0-9])?.*)-{architecture}.zip", file).group(1) - num_version = small_version.replace('.', '') - python_version_dir = os.path.join(resources_dir, f"python{num_version}") - # Creates the python version folder if it doesn't exists - if not os.path.exists(python_version_dir): - os.makedirs(python_version_dir) - file_name = f"python3-{platform}-{small_version}-{architecture}.rc" - with open(os.path.join(python_version_dir, file_name), 'w', encoding='cp1252') as fh: - # python38 RCDATA "..\..\python\python3-android-3.8.15-arm64.zip" - fh.write(f"python{num_version} RCDATA \"..\\..\\python\\python3-{platform}-{full_version}-{architecture}.zip\"") \ No newline at end of file diff --git a/python/macos/3.10.9/arm/libpython3.10.dylib b/python/macos/3.10.9/arm/libpython3.10.dylib deleted file mode 100644 index 3979290..0000000 Binary files a/python/macos/3.10.9/arm/libpython3.10.dylib and /dev/null differ diff --git a/python/macos/3.10.9/arm/python3.10 b/python/macos/3.10.9/arm/python3.10 deleted file mode 100644 index 56914db..0000000 Binary files a/python/macos/3.10.9/arm/python3.10 and /dev/null differ diff --git a/python/macos/3.10.9/intel/libpython3.10.dylib b/python/macos/3.10.9/intel/libpython3.10.dylib deleted file mode 100644 index 8620eeb..0000000 Binary files a/python/macos/3.10.9/intel/libpython3.10.dylib and /dev/null differ diff --git a/python/macos/3.10.9/intel/python3.10 b/python/macos/3.10.9/intel/python3.10 deleted file mode 100644 index 4a9424a..0000000 Binary files a/python/macos/3.10.9/intel/python3.10 and /dev/null differ diff --git a/python/macos/3.11.2/arm/libpython3.11.dylib b/python/macos/3.11.2/arm/libpython3.11.dylib deleted file mode 100644 index 843ebdd..0000000 Binary files a/python/macos/3.11.2/arm/libpython3.11.dylib and /dev/null differ diff --git a/python/macos/3.11.2/arm/python3.11 b/python/macos/3.11.2/arm/python3.11 deleted file mode 100644 index 124cf22..0000000 Binary files a/python/macos/3.11.2/arm/python3.11 and /dev/null differ diff --git a/python/macos/3.11.2/intel/libpython3.11.dylib b/python/macos/3.11.2/intel/libpython3.11.dylib deleted file mode 100644 index dab049e..0000000 Binary files a/python/macos/3.11.2/intel/libpython3.11.dylib and /dev/null differ diff --git a/python/macos/3.11.2/intel/python3.11 b/python/macos/3.11.2/intel/python3.11 deleted file mode 100644 index becb08d..0000000 Binary files a/python/macos/3.11.2/intel/python3.11 and /dev/null differ diff --git a/python/macos/3.7.16/intel/libpython3.7m.dylib b/python/macos/3.7.16/intel/libpython3.7m.dylib deleted file mode 100644 index 9fc5056..0000000 Binary files a/python/macos/3.7.16/intel/libpython3.7m.dylib and /dev/null differ diff --git a/python/macos/3.7.16/intel/python3.7m b/python/macos/3.7.16/intel/python3.7m deleted file mode 100644 index 1d5f97f..0000000 Binary files a/python/macos/3.7.16/intel/python3.7m and /dev/null differ diff --git a/python/macos/3.8.16/arm/libpython3.8.dylib b/python/macos/3.8.16/arm/libpython3.8.dylib deleted file mode 100644 index 9b43dcd..0000000 Binary files a/python/macos/3.8.16/arm/libpython3.8.dylib and /dev/null differ diff --git a/python/macos/3.8.16/arm/python3.8 b/python/macos/3.8.16/arm/python3.8 deleted file mode 100644 index 3f78826..0000000 Binary files a/python/macos/3.8.16/arm/python3.8 and /dev/null differ diff --git a/python/macos/3.8.16/intel/libpython3.8.dylib b/python/macos/3.8.16/intel/libpython3.8.dylib deleted file mode 100644 index 13d6599..0000000 Binary files a/python/macos/3.8.16/intel/libpython3.8.dylib and /dev/null differ diff --git a/python/macos/3.8.16/intel/python3.8 b/python/macos/3.8.16/intel/python3.8 deleted file mode 100644 index 4ffbfa8..0000000 Binary files a/python/macos/3.8.16/intel/python3.8 and /dev/null differ diff --git a/python/macos/3.9.16/arm/libpython3.9.dylib b/python/macos/3.9.16/arm/libpython3.9.dylib deleted file mode 100644 index 74ffae1..0000000 Binary files a/python/macos/3.9.16/arm/libpython3.9.dylib and /dev/null differ diff --git a/python/macos/3.9.16/arm/python3.9 b/python/macos/3.9.16/arm/python3.9 deleted file mode 100644 index 9930fad..0000000 Binary files a/python/macos/3.9.16/arm/python3.9 and /dev/null differ diff --git a/python/macos/3.9.16/intel/libpython3.9.dylib b/python/macos/3.9.16/intel/libpython3.9.dylib deleted file mode 100644 index 45209e3..0000000 Binary files a/python/macos/3.9.16/intel/libpython3.9.dylib and /dev/null differ diff --git a/python/macos/3.9.16/intel/python3.9 b/python/macos/3.9.16/intel/python3.9 deleted file mode 100644 index 1fb89b1..0000000 Binary files a/python/macos/3.9.16/intel/python3.9 and /dev/null differ diff --git a/python/provide_deliverables.py b/python/provide_deliverables.py deleted file mode 100644 index af97a7f..0000000 --- a/python/provide_deliverables.py +++ /dev/null @@ -1,134 +0,0 @@ -import os -import re -from io import StringIO - -delphi_platform = { - "windows-win32": "Win32", - "windows-amd64": "Win64", - "android-arm": "Android", - "android-arm64": "Android64", - "macos-x86_64": "OSX64", - "macos-universal2": "OSXARM64", - "linux-x86_64": "Linux64", -} - -android_arch_to_abi = { - "arm": "armeabi-v7a", - "arm64": "arm64-v8a", -} - -def sort_rule(a): - if ('windows' in a) and 'win32' in a: - return (1, a) - elif ('windows' in a) and 'amd64' in a: - return (2, a) - elif ('android' in a) and 'arm' in a: - return (3, a) - elif ('android' in a) and ('arm64' in a): - return (4, a) - elif ('macos' in a) and ('x86_64' in a): - return (5, a) - elif ('macos' in a) and ('universal2' in a): - return (6, a) - elif 'linux' in a: - return (7, a) - else: - return (8, a) - -def begin(path, version): - #fh = open(os.path.join(path, version + ".txt"), "w", encoding="cp1252") - fh = StringIO() - fh.write(f"FDeployableFiles.Add('{version}', [") - return fh - -def write(file, small_version, full_version, platform, architecture): - offset = " " - dplatform = delphi_platform[platform + "-" + architecture] - - file.write("\n") - file.write(f"{offset}//{platform.capitalize()}-{architecture}") - file.write("\n") - - if platform == "windows": - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\python3-{platform}-{full_version}-{architecture}.zip', '.\\', True, True, TDeployOperation.doCopyOnly, ''),") - elif platform == "android": - abi = android_arch_to_abi[architecture] - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\python3-{platform}-{full_version}-{architecture}.zip', '.\\assets\\internal', False, True, TDeployOperation.doCopyOnly, ''),") - file.write("\n") - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\{platform}\\{full_version}\\{architecture}\\libpython{small_version}.so', 'library\\lib\\{abi}\\', False, True, TDeployOperation.doSetExecBit, ''),") - file.write("\n") - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\{platform}\\{full_version}\\{architecture}\\libpythonlauncher{small_version}.so', 'library\\lib\\{abi}\\', False, True, TDeployOperation.doSetExecBit, ''),") - - # We must include arm for arm64 too - if architecture == "arm64": - abi = android_arch_to_abi["arm"] - file.write("\n") - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\python3-{platform}-{full_version}-arm.zip', '.\\assets\\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''),") - file.write("\n") - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\{platform}\\{full_version}\\arm\\libpython{small_version}.so', 'library\\lib\\{abi}\\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''),") - file.write("\n") - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\{platform}\\{full_version}\\arm\\libpythonlauncher{small_version}.so', 'library\\lib\\{abi}\\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''),") - - elif platform == "macos": - arch_name_folder = "intel" - if architecture == "universal2": - arch_name_folder = "arm" - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\python3-{platform}-{full_version}-{architecture}.zip', 'Contents\\Resources\\', True, True, TDeployOperation.doCopyOnly, ''),") - file.write("\n") - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\{platform}\\{full_version}\\{arch_name_folder}\\libpython{small_version}.dylib','Contents\\MacOS\\', True, True, TDeployOperation.doSetExecBit, ''),") - file.write("\n") - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\{platform}\\{full_version}\\{arch_name_folder}\\python{small_version}','Contents\\MacOS\\', True, True, TDeployOperation.doSetExecBit, ''),") - elif platform == "linux": - file.write(f"{offset}TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.{dplatform}, 'python\\python3-linux-{full_version}-x86_64.zip', '.\\', True, True, TDeployOperation.doCopyOnly, ''),") - -def end(file): - # Remove the last , - val = "".join(file.getvalue().rsplit(",", 1)) - file.seek(0) - file.write(val) - file.write("\n") - file.write("]);") - file.write("\n\n") - val = file.getvalue() - file.close() - return val - -cur_dir = os.path.realpath(os.path.dirname(__file__)) - -out_file = os.path.join(cur_dir, "deliverables_cmds.txt") -if os.path.exists(out_file): - os.remove(out_file) - -# Sort all versions -ver_set = set([]) -for root, dirs, files in os.walk(cur_dir): - for file in files: - version = re.search(f"python3-.*-([0-9].[0-9]([0-9])?.*)-.*.zip", file) - if not version: - continue - found_ver = version.group(1) - major_minor_nums = found_ver.split(".")[0:2] - ver = major_minor_nums[0] + "." + major_minor_nums[1] - ver_set.add(ver) - -with open(out_file, 'w', encoding="cp1252") as fh_out: - # Create the command by each version - # We create the version pairs to sort the version set - ver_pairs = [(float((ver.replace('.', ''))), ver) for ver in ver_set] - # We use the float key to sort the set - sorted_ver_pair = sorted(ver_pairs, key=lambda x:x[0]) - # Using the sorted value (val = original version) - for small_version in [val for _, val in sorted_ver_pair]: - fh = begin(cur_dir, small_version) - try: - re_ver = re.compile(f'python3-.*-{small_version}.*-.*.zip') - filtered_files = filter(lambda name: (name.startswith('python3-') and name.endswith('.zip')), os.listdir(cur_dir)) - sorted_files = sorted(list(filtered_files), key=sort_rule) - for file in sorted_files: - if re_ver.match(file): - platform = re.search(f"python3-(.*)-{small_version}", file).group(1) - architecture = re.search("(.*).zip", file[file.rindex('-')+1:]).group(1) - full_version = re.search(f"python3-{platform}-([0-9].[0-9]([0-9])?.*)-{architecture}.zip", file).group(1) - write(fh, small_version, full_version, platform, architecture) - finally: - fh_out.write(end(fh)) \ No newline at end of file diff --git a/python/python3-android-3.10.7-arm.zip b/python/python3-android-3.10.7-arm.zip index c25b3db..288c3f6 100644 Binary files a/python/python3-android-3.10.7-arm.zip and b/python/python3-android-3.10.7-arm.zip differ diff --git a/python/python3-android-3.10.7-arm64.zip b/python/python3-android-3.10.7-arm64.zip index 067cbe4..250a736 100644 Binary files a/python/python3-android-3.10.7-arm64.zip and b/python/python3-android-3.10.7-arm64.zip differ diff --git a/python/python3-android-3.11.2-arm.zip b/python/python3-android-3.11.2-arm.zip index 8fdcddb..73917e4 100644 Binary files a/python/python3-android-3.11.2-arm.zip and b/python/python3-android-3.11.2-arm.zip differ diff --git a/python/python3-android-3.11.2-arm64.zip b/python/python3-android-3.11.2-arm64.zip index 19a5d10..9ee8480 100644 Binary files a/python/python3-android-3.11.2-arm64.zip and b/python/python3-android-3.11.2-arm64.zip differ diff --git a/python/python3-android-3.12.0-arm.zip b/python/python3-android-3.12.0-arm.zip new file mode 100644 index 0000000..8ca340f --- /dev/null +++ b/python/python3-android-3.12.0-arm.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bf888c0d93aea1ab7486d37047803607bcd4fd0bd74787bafe3e480b883a68be +size 34541318 diff --git a/python/python3-android-3.12.0-arm64.zip b/python/python3-android-3.12.0-arm64.zip new file mode 100644 index 0000000..073e871 --- /dev/null +++ b/python/python3-android-3.12.0-arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7925f7051f072d97780ff154104c2d1ae894d1807a3e50f3c60e63a9d773c428 +size 34300008 diff --git a/python/python3-android-3.7.16-arm.zip b/python/python3-android-3.7.16-arm.zip deleted file mode 100644 index 6272c90..0000000 Binary files a/python/python3-android-3.7.16-arm.zip and /dev/null differ diff --git a/python/python3-android-3.7.16-arm64.zip b/python/python3-android-3.7.16-arm64.zip deleted file mode 100644 index d4619b0..0000000 Binary files a/python/python3-android-3.7.16-arm64.zip and /dev/null differ diff --git a/python/python3-android-3.8.16-arm.zip b/python/python3-android-3.8.16-arm.zip index f3be58b..9814052 100644 Binary files a/python/python3-android-3.8.16-arm.zip and b/python/python3-android-3.8.16-arm.zip differ diff --git a/python/python3-android-3.8.16-arm64.zip b/python/python3-android-3.8.16-arm64.zip index 8eac19a..cd00320 100644 Binary files a/python/python3-android-3.8.16-arm64.zip and b/python/python3-android-3.8.16-arm64.zip differ diff --git a/python/python3-android-3.9.16-arm.zip b/python/python3-android-3.9.16-arm.zip index 80773df..de56385 100644 Binary files a/python/python3-android-3.9.16-arm.zip and b/python/python3-android-3.9.16-arm.zip differ diff --git a/python/python3-android-3.9.16-arm64.zip b/python/python3-android-3.9.16-arm64.zip index 3fcc284..ad14cd9 100644 Binary files a/python/python3-android-3.9.16-arm64.zip and b/python/python3-android-3.9.16-arm64.zip differ diff --git a/python/python3-ios-3.10.13-iphoneos.arm64.zip b/python/python3-ios-3.10.13-iphoneos.arm64.zip new file mode 100644 index 0000000..32d3044 --- /dev/null +++ b/python/python3-ios-3.10.13-iphoneos.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f17b7b27ce73085a2f1371e0797c75f17c85697f30a94cae0e8b800cd71dafc +size 61635662 diff --git a/python/python3-ios-3.10.13-iphonesimulator.arm64.zip b/python/python3-ios-3.10.13-iphonesimulator.arm64.zip new file mode 100644 index 0000000..a429bb5 --- /dev/null +++ b/python/python3-ios-3.10.13-iphonesimulator.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5853723a7fc49de7f16b1b62ac9ef3029601aac6f5835c2fc1f83634ca3f2f42 +size 61884368 diff --git a/python/python3-ios-3.11.6-iphoneos.arm64.zip b/python/python3-ios-3.11.6-iphoneos.arm64.zip new file mode 100644 index 0000000..dec21e8 --- /dev/null +++ b/python/python3-ios-3.11.6-iphoneos.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ddf214fbbfb5569a0db5211385b6f8a4656f064530a16b7234fd53bd24c32ecf +size 86172929 diff --git a/python/python3-ios-3.11.6-iphonesimulator.arm64.zip b/python/python3-ios-3.11.6-iphonesimulator.arm64.zip new file mode 100644 index 0000000..6adca0d --- /dev/null +++ b/python/python3-ios-3.11.6-iphonesimulator.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f5d7b1ab203a129a0d5fa67fe75ab13de73383386c53aa5f3a5e1f4d72f1e42 +size 86453311 diff --git a/python/python3-ios-3.12.0-iphoneos.arm64.zip b/python/python3-ios-3.12.0-iphoneos.arm64.zip new file mode 100644 index 0000000..0c56ce2 --- /dev/null +++ b/python/python3-ios-3.12.0-iphoneos.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2158a105456b959ebff6ade05c830b29717cc0120cc3d3388b88ca61eb71c590 +size 89184659 diff --git a/python/python3-ios-3.12.0-iphonesimulator.arm64.zip b/python/python3-ios-3.12.0-iphonesimulator.arm64.zip new file mode 100644 index 0000000..7e33447 --- /dev/null +++ b/python/python3-ios-3.12.0-iphonesimulator.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f8f54925faaa56969d67d47342aecdcfdf382d7cd16e0b64ea9ec7eb278f8be +size 89498475 diff --git a/python/python3-ios-3.8.18-iphoneos.arm64.zip b/python/python3-ios-3.8.18-iphoneos.arm64.zip new file mode 100644 index 0000000..e114ede --- /dev/null +++ b/python/python3-ios-3.8.18-iphoneos.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bab1e7f7f820e2542ad89e7eb8facbc35a88711b43ad798345d0faae26e851f +size 58957345 diff --git a/python/python3-ios-3.8.18-iphonesimulator.arm64.zip b/python/python3-ios-3.8.18-iphonesimulator.arm64.zip new file mode 100644 index 0000000..d9b5c65 --- /dev/null +++ b/python/python3-ios-3.8.18-iphonesimulator.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:66d4b05a37629a1a38a0ac548bb1f888f4cb4b554c9f1535c674d08004fdca05 +size 59192929 diff --git a/python/python3-ios-3.9.18-iphoneos.arm64.zip b/python/python3-ios-3.9.18-iphoneos.arm64.zip new file mode 100644 index 0000000..dd91ad7 --- /dev/null +++ b/python/python3-ios-3.9.18-iphoneos.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6985e2ff402ccd9ba9d9ff8c13d1289ba2d6ce12883148373ff312c05c8d9ab7 +size 58654197 diff --git a/python/python3-ios-3.9.18-iphonesimulator.arm64.zip b/python/python3-ios-3.9.18-iphonesimulator.arm64.zip new file mode 100644 index 0000000..accd5eb --- /dev/null +++ b/python/python3-ios-3.9.18-iphonesimulator.arm64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:047292558f28f00932bea66e1d66ca0d6a31da595bc1284095f850d9514a0b9e +size 58897733 diff --git a/python/python3-linux-3.10.9-x86_64.zip b/python/python3-linux-3.10.9-x86_64.zip index 2358e94..408ceb0 100644 Binary files a/python/python3-linux-3.10.9-x86_64.zip and b/python/python3-linux-3.10.9-x86_64.zip differ diff --git a/python/python3-linux-3.11.2-x86_64.zip b/python/python3-linux-3.11.2-x86_64.zip index a87d745..32ccd90 100644 Binary files a/python/python3-linux-3.11.2-x86_64.zip and b/python/python3-linux-3.11.2-x86_64.zip differ diff --git a/python/python3-linux-3.12.0-x86_64.zip b/python/python3-linux-3.12.0-x86_64.zip new file mode 100644 index 0000000..826dcd3 --- /dev/null +++ b/python/python3-linux-3.12.0-x86_64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6204fd98847d3362270c3080ed2b7b77acf7b35109b8fd9b3c445b4271728b5e +size 42723659 diff --git a/python/python3-linux-3.7.16-x86_64.zip b/python/python3-linux-3.7.16-x86_64.zip deleted file mode 100644 index 518b8f2..0000000 Binary files a/python/python3-linux-3.7.16-x86_64.zip and /dev/null differ diff --git a/python/python3-linux-3.8.16-x86_64.zip b/python/python3-linux-3.8.16-x86_64.zip index 6222da9..f33fc78 100644 Binary files a/python/python3-linux-3.8.16-x86_64.zip and b/python/python3-linux-3.8.16-x86_64.zip differ diff --git a/python/python3-linux-3.9.16-x86_64.zip b/python/python3-linux-3.9.16-x86_64.zip index c1912b6..2793b8e 100644 Binary files a/python/python3-linux-3.9.16-x86_64.zip and b/python/python3-linux-3.9.16-x86_64.zip differ diff --git a/python/python3-macos-3.10.13-universal2.zip b/python/python3-macos-3.10.13-universal2.zip new file mode 100644 index 0000000..0a20fc5 --- /dev/null +++ b/python/python3-macos-3.10.13-universal2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6dfa8ded4c5186cad311595afd14747a72e87018cd85e083869f1f0dc3a5453 +size 87153547 diff --git a/python/python3-macos-3.10.9-universal2.zip b/python/python3-macos-3.10.9-universal2.zip deleted file mode 100644 index e08b5ee..0000000 Binary files a/python/python3-macos-3.10.9-universal2.zip and /dev/null differ diff --git a/python/python3-macos-3.10.9-x86_64.zip b/python/python3-macos-3.10.9-x86_64.zip deleted file mode 100644 index 46d21c2..0000000 Binary files a/python/python3-macos-3.10.9-x86_64.zip and /dev/null differ diff --git a/python/python3-macos-3.11.2-universal2.zip b/python/python3-macos-3.11.2-universal2.zip deleted file mode 100644 index 7f0c44c..0000000 Binary files a/python/python3-macos-3.11.2-universal2.zip and /dev/null differ diff --git a/python/python3-macos-3.11.2-x86_64.zip b/python/python3-macos-3.11.2-x86_64.zip deleted file mode 100644 index d439fdf..0000000 Binary files a/python/python3-macos-3.11.2-x86_64.zip and /dev/null differ diff --git a/python/python3-macos-3.11.6-universal2.zip b/python/python3-macos-3.11.6-universal2.zip new file mode 100644 index 0000000..49a9681 --- /dev/null +++ b/python/python3-macos-3.11.6-universal2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd233de93e5d7ee45276c8f4dd1f23bec015e85301e57a19559178a7429ccc5b +size 117433456 diff --git a/python/python3-macos-3.12.0-universal2.zip b/python/python3-macos-3.12.0-universal2.zip new file mode 100644 index 0000000..9f2f842 --- /dev/null +++ b/python/python3-macos-3.12.0-universal2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea4e7a6ee6c40c68dcad9da72999b008fb3c671c430d298aa8d0cab43b1b3ec5 +size 127220342 diff --git a/python/python3-macos-3.7.16-x86_64.zip b/python/python3-macos-3.7.16-x86_64.zip deleted file mode 100644 index 7ed2ae2..0000000 Binary files a/python/python3-macos-3.7.16-x86_64.zip and /dev/null differ diff --git a/python/python3-macos-3.8.16-universal2.zip b/python/python3-macos-3.8.16-universal2.zip deleted file mode 100644 index 3bf72be..0000000 Binary files a/python/python3-macos-3.8.16-universal2.zip and /dev/null differ diff --git a/python/python3-macos-3.8.16-x86_64.zip b/python/python3-macos-3.8.16-x86_64.zip deleted file mode 100644 index 85d845c..0000000 Binary files a/python/python3-macos-3.8.16-x86_64.zip and /dev/null differ diff --git a/python/python3-macos-3.8.18-universal2.zip b/python/python3-macos-3.8.18-universal2.zip new file mode 100644 index 0000000..5f384f1 --- /dev/null +++ b/python/python3-macos-3.8.18-universal2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a17bc7276633bf4ae4a209d1a5787d61ea212f611656a16292500b59f907fb66 +size 81612844 diff --git a/python/python3-macos-3.9.16-universal2.zip b/python/python3-macos-3.9.16-universal2.zip deleted file mode 100644 index 94fe2e9..0000000 Binary files a/python/python3-macos-3.9.16-universal2.zip and /dev/null differ diff --git a/python/python3-macos-3.9.16-x86_64.zip b/python/python3-macos-3.9.16-x86_64.zip deleted file mode 100644 index 730d64b..0000000 Binary files a/python/python3-macos-3.9.16-x86_64.zip and /dev/null differ diff --git a/python/python3-macos-3.9.18-universal2.zip b/python/python3-macos-3.9.18-universal2.zip new file mode 100644 index 0000000..8d912a6 --- /dev/null +++ b/python/python3-macos-3.9.18-universal2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7fed4c1febb2ebdf87d8d67cae842f52d31da5727f0a156aeb9cf403f091ed20 +size 83216084 diff --git a/python/python3-windows-3.10.9-amd64.zip b/python/python3-windows-3.10.9-amd64.zip index e8a156d..8d01427 100644 Binary files a/python/python3-windows-3.10.9-amd64.zip and b/python/python3-windows-3.10.9-amd64.zip differ diff --git a/python/python3-windows-3.10.9-win32.zip b/python/python3-windows-3.10.9-win32.zip index b1ed5a0..c5cd114 100644 Binary files a/python/python3-windows-3.10.9-win32.zip and b/python/python3-windows-3.10.9-win32.zip differ diff --git a/python/python3-windows-3.11.2-amd64.zip b/python/python3-windows-3.11.2-amd64.zip index 68b6a49..0015ee9 100644 Binary files a/python/python3-windows-3.11.2-amd64.zip and b/python/python3-windows-3.11.2-amd64.zip differ diff --git a/python/python3-windows-3.11.2-win32.zip b/python/python3-windows-3.11.2-win32.zip index cbeb973..78942f0 100644 Binary files a/python/python3-windows-3.11.2-win32.zip and b/python/python3-windows-3.11.2-win32.zip differ diff --git a/python/python3-windows-3.12.0-amd64.zip b/python/python3-windows-3.12.0-amd64.zip new file mode 100644 index 0000000..186064c --- /dev/null +++ b/python/python3-windows-3.12.0-amd64.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa1a81806e50dda5b9f5cc7aa90a694d89ef517df6d6081714ee657e8cbbf86c +size 10833601 diff --git a/python/python3-windows-3.12.0-win32.zip b/python/python3-windows-3.12.0-win32.zip new file mode 100644 index 0000000..da39fe6 --- /dev/null +++ b/python/python3-windows-3.12.0-win32.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2a60d34ae7f0de62a0a9a2dfc7a116bf95bb20a209871999ad5a019cb6235d52 +size 9670938 diff --git a/python/python3-windows-3.7.9-amd64.zip b/python/python3-windows-3.7.9-amd64.zip deleted file mode 100644 index d3beb6a..0000000 Binary files a/python/python3-windows-3.7.9-amd64.zip and /dev/null differ diff --git a/python/python3-windows-3.7.9-win32.zip b/python/python3-windows-3.7.9-win32.zip deleted file mode 100644 index 4737cb3..0000000 Binary files a/python/python3-windows-3.7.9-win32.zip and /dev/null differ diff --git a/python/python3-windows-3.8.10-amd64.zip b/python/python3-windows-3.8.10-amd64.zip index d3c1734..da00b5c 100644 Binary files a/python/python3-windows-3.8.10-amd64.zip and b/python/python3-windows-3.8.10-amd64.zip differ diff --git a/python/python3-windows-3.8.10-win32.zip b/python/python3-windows-3.8.10-win32.zip index 7a1ab33..932f6b6 100644 Binary files a/python/python3-windows-3.8.10-win32.zip and b/python/python3-windows-3.8.10-win32.zip differ diff --git a/python/python3-windows-3.9.13-amd64.zip b/python/python3-windows-3.9.13-amd64.zip index fc00306..31391d2 100644 Binary files a/python/python3-windows-3.9.13-amd64.zip and b/python/python3-windows-3.9.13-amd64.zip differ diff --git a/python/python3-windows-3.9.13-win32.zip b/python/python3-windows-3.9.13-win32.zip index 5a70a2e..2f2b9c5 100644 Binary files a/python/python3-windows-3.9.13-win32.zip and b/python/python3-windows-3.9.13-win32.zip differ diff --git a/resources/python310/python3-macos-3.10-universal2.rc b/resources/python310/python3-macos-3.10-universal2.rc index 749e8e4..a31c5e8 100644 --- a/resources/python310/python3-macos-3.10-universal2.rc +++ b/resources/python310/python3-macos-3.10-universal2.rc @@ -1 +1 @@ -python310 RCDATA "..\..\python\python3-macos-3.10.9-universal2.zip" \ No newline at end of file +python310 RCDATA "..\..\python\python3-macos-3.10.13-universal2.zip" \ No newline at end of file diff --git a/resources/python310/python3-macos-3.10-x86_64.rc b/resources/python310/python3-macos-3.10-x86_64.rc index 0697a28..a31c5e8 100644 --- a/resources/python310/python3-macos-3.10-x86_64.rc +++ b/resources/python310/python3-macos-3.10-x86_64.rc @@ -1 +1 @@ -python310 RCDATA "..\..\python\python3-macos-3.10.9-x86_64.zip" \ No newline at end of file +python310 RCDATA "..\..\python\python3-macos-3.10.13-universal2.zip" \ No newline at end of file diff --git a/resources/python311/python3-macos-3.11-universal2.rc b/resources/python311/python3-macos-3.11-universal2.rc index 306e356..130d59a 100644 --- a/resources/python311/python3-macos-3.11-universal2.rc +++ b/resources/python311/python3-macos-3.11-universal2.rc @@ -1 +1 @@ -python311 RCDATA "..\..\python\python3-macos-3.11.2-universal2.zip" \ No newline at end of file +python311 RCDATA "..\..\python\python3-macos-3.11.6-universal2.zip" \ No newline at end of file diff --git a/resources/python311/python3-macos-3.11-x86_64.rc b/resources/python311/python3-macos-3.11-x86_64.rc index 2e2687b..130d59a 100644 --- a/resources/python311/python3-macos-3.11-x86_64.rc +++ b/resources/python311/python3-macos-3.11-x86_64.rc @@ -1 +1 @@ -python311 RCDATA "..\..\python\python3-macos-3.11.2-x86_64.zip" \ No newline at end of file +python311 RCDATA "..\..\python\python3-macos-3.11.6-universal2.zip" \ No newline at end of file diff --git a/resources/python37/python3-android-3.7-arm.rc b/resources/python37/python3-android-3.7-arm.rc deleted file mode 100644 index 0a26d37..0000000 --- a/resources/python37/python3-android-3.7-arm.rc +++ /dev/null @@ -1 +0,0 @@ -python37 RCDATA "..\..\python\python3-android-3.7.16-arm.zip" \ No newline at end of file diff --git a/resources/python37/python3-android-3.7-arm64.rc b/resources/python37/python3-android-3.7-arm64.rc deleted file mode 100644 index 2e03b25..0000000 --- a/resources/python37/python3-android-3.7-arm64.rc +++ /dev/null @@ -1 +0,0 @@ -python37 RCDATA "..\..\python\python3-android-3.7.16-arm64.zip" \ No newline at end of file diff --git a/resources/python37/python3-linux-3.7-x86_64.rc b/resources/python37/python3-linux-3.7-x86_64.rc deleted file mode 100644 index 19b6753..0000000 --- a/resources/python37/python3-linux-3.7-x86_64.rc +++ /dev/null @@ -1 +0,0 @@ -python37 RCDATA "..\..\python\python3-linux-3.7.16-x86_64.zip" \ No newline at end of file diff --git a/resources/python37/python3-macos-3.7-x86_64.rc b/resources/python37/python3-macos-3.7-x86_64.rc deleted file mode 100644 index b028d80..0000000 --- a/resources/python37/python3-macos-3.7-x86_64.rc +++ /dev/null @@ -1 +0,0 @@ -python37 RCDATA "..\..\python\python3-macos-3.7.16-x86_64.zip" \ No newline at end of file diff --git a/resources/python37/python3-windows-3.7-amd64.rc b/resources/python37/python3-windows-3.7-amd64.rc deleted file mode 100644 index eeccd1e..0000000 --- a/resources/python37/python3-windows-3.7-amd64.rc +++ /dev/null @@ -1 +0,0 @@ -python37 RCDATA "..\..\python\python3-windows-3.7.9-amd64.zip" \ No newline at end of file diff --git a/resources/python37/python3-windows-3.7-win32.rc b/resources/python37/python3-windows-3.7-win32.rc deleted file mode 100644 index df92468..0000000 --- a/resources/python37/python3-windows-3.7-win32.rc +++ /dev/null @@ -1 +0,0 @@ -python37 RCDATA "..\..\python\python3-windows-3.7.9-win32.zip" \ No newline at end of file diff --git a/resources/python38/python3-macos-3.8-universal2.rc b/resources/python38/python3-macos-3.8-universal2.rc index 6939502..a6c2341 100644 --- a/resources/python38/python3-macos-3.8-universal2.rc +++ b/resources/python38/python3-macos-3.8-universal2.rc @@ -1 +1 @@ -python38 RCDATA "..\..\python\python3-macos-3.8.16-universal2.zip" \ No newline at end of file +python38 RCDATA "..\..\python\python3-macos-3.8.18-universal2.zip" \ No newline at end of file diff --git a/resources/python38/python3-macos-3.8-x86_64.rc b/resources/python38/python3-macos-3.8-x86_64.rc index 928e3d4..a6c2341 100644 --- a/resources/python38/python3-macos-3.8-x86_64.rc +++ b/resources/python38/python3-macos-3.8-x86_64.rc @@ -1 +1 @@ -python38 RCDATA "..\..\python\python3-macos-3.8.16-x86_64.zip" \ No newline at end of file +python38 RCDATA "..\..\python\python3-macos-3.8.18-universal2.zip" \ No newline at end of file diff --git a/resources/python39/python3-macos-3.9-universal2.rc b/resources/python39/python3-macos-3.9-universal2.rc index 667be0b..6f5539d 100644 --- a/resources/python39/python3-macos-3.9-universal2.rc +++ b/resources/python39/python3-macos-3.9-universal2.rc @@ -1 +1 @@ -python39 RCDATA "..\..\python\python3-macos-3.9.16-universal2.zip" \ No newline at end of file +python39 RCDATA "..\..\python\python3-macos-3.9.18-universal2.zip" \ No newline at end of file diff --git a/resources/python39/python3-macos-3.9-x86_64.rc b/resources/python39/python3-macos-3.9-x86_64.rc index d698945..6f5539d 100644 --- a/resources/python39/python3-macos-3.9-x86_64.rc +++ b/resources/python39/python3-macos-3.9-x86_64.rc @@ -1 +1 @@ -python39 RCDATA "..\..\python\python3-macos-3.9.16-x86_64.zip" \ No newline at end of file +python39 RCDATA "..\..\python\python3-macos-3.9.18-universal2.zip" \ No newline at end of file diff --git a/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas b/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas index d020127..7486b81 100644 --- a/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas +++ b/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas @@ -1,166 +1,166 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.AddOn.EnsurePip' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment EnsurePIP add-on *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.AddOn.EnsurePip; - -interface - -uses - System.SysUtils, - System.Classes, - PyTools.Cancelation, - PyEnvironment, - PyEnvironment.AddOn; - -type - [ComponentPlatforms(pidAllPlatforms)] - TPyEnvironmentAddOnEnsurePip = class(TPyEnvironmentCustomAddOn) - private - FVerbose: boolean; - FUpgrade: boolean; - protected - function GetInfo(): TPyPluginInfo; override; - function IsInstalled(): boolean; override; - procedure SetTriggers(const Value: TPyEnvironmentaddOnTriggers); override; - procedure InternalExecute(const ACancelation: ICancelation); override; - public - constructor Create(AOwner: TComponent); override; - published - property Triggers default [TPyEnvironmentaddOnTrigger.trAfterSetup]; - property Verbose: boolean read FVerbose write FVerbose; - property Upgrade: boolean read FUpgrade write FUpgrade; - end; - -implementation - -uses - System.IOUtils, - System.SyncObjs, - PythonEngine, - PyTools.ExecCmd, - PyTools.ExecCmd.Args, - PyEnvironment.Exception; - -{ TPyEnvironmentAddOnEnsurePip } - -procedure TPyEnvironmentAddOnEnsurePip.SetTriggers( - const Value: TPyEnvironmentaddOnTriggers); -begin - inherited SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); -end; - -constructor TPyEnvironmentAddOnEnsurePip.Create(AOwner: TComponent); -begin - SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); - inherited; -end; - -function TPyEnvironmentAddOnEnsurePip.GetInfo: TPyPluginInfo; -begin - Result.Name := 'ensurepip'; - Result.Description := - 'Provides support for bootstrapping the pip installer into an existing ' - + 'Python installation or virtual environment.' - + sLineBreak - + 'See more: https://pip.pypa.io/en/stable/installation/#ensurepip'; - Result.InstallsWhen := [TPyPluginEvent.AfterActivate]; -end; - -function TPyEnvironmentAddOnEnsurePip.IsInstalled: boolean; -var - LPythonHome: string; - LExecutable: string; - LSharedLibrary: string; - LCmd: IExecCmd; -begin - LPythonHome := GetPythonEngine().PythonHome; - LExecutable := GetPythonEngine().ProgramName; - LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, - GetPythonEngine().DllName); - - LCmd := TExecCmdService.Cmd( - LExecutable, - TExecCmdArgs.BuildArgv( - LExecutable, ['-m', 'pip', '--version']), - TExecCmdArgs.BuildEnvp( - LPythonHome, - LExecutable, - LSharedLibrary) - ).Run(); - - Result := (LCmd.Wait() = EXIT_SUCCESS); -end; - -procedure TPyEnvironmentAddOnEnsurePip.InternalExecute( - const ACancelation: ICancelation); -var - LPythonHome: string; - LExecutable: string; - LSharedLibrary: string; - LInput: TArray; - LCmd: IExecCmd; -begin - inherited; - LPythonHome := GetPythonEngine().PythonHome; - LExecutable := GetPythonEngine().ProgramName; - LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, - GetPythonEngine().DllName); - - LInput := ['-m', 'ensurepip']; - - if FVerbose then - LInput := LInput + ['--verbose']; - - if FUpgrade then - LInput := LInput + ['--upgrade']; - - LCmd := TExecCmdService.Cmd( - LExecutable, - TExecCmdArgs.BuildArgv( - LExecutable, LInput), - TExecCmdArgs.BuildEnvp( - LPythonHome, - LExecutable, - LSharedLibrary)) - .Run([TRedirect.stdout, TRedirect.stderr]); - - TSpinWait.SpinUntil(function(): boolean begin - Result := not LCmd.IsAlive or ACancelation.IsCancelled; - end); - - ACancelation.CheckCancelled(); - - if (LCmd.Wait() <> EXIT_SUCCESS) then - raise EPipSetupFailed.Create('PIP setup has failed.' - + #13#10 - + LCmd.Output); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.AddOn.EnsurePip' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment EnsurePIP add-on *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.AddOn.EnsurePip; + +interface + +uses + System.SysUtils, + System.Classes, + PyTools.Cancelation, + PyEnvironment, + PyEnvironment.AddOn; + +type + [ComponentPlatforms(pidAllPlatforms)] + TPyEnvironmentAddOnEnsurePip = class(TPyEnvironmentCustomAddOn) + private + FVerbose: boolean; + FUpgrade: boolean; + protected + function GetInfo(): TPyPluginInfo; override; + function IsInstalled(): boolean; override; + procedure SetTriggers(const Value: TPyEnvironmentaddOnTriggers); override; + procedure InternalExecute(const ACancelation: ICancelation); override; + public + constructor Create(AOwner: TComponent); override; + published + property Triggers default [TPyEnvironmentaddOnTrigger.trAfterSetup]; + property Verbose: boolean read FVerbose write FVerbose; + property Upgrade: boolean read FUpgrade write FUpgrade; + end; + +implementation + +uses + System.IOUtils, + System.SyncObjs, + PythonEngine, + PyTools.ExecCmd, + PyTools.ExecCmd.Args, + PyEnvironment.Exception; + +{ TPyEnvironmentAddOnEnsurePip } + +procedure TPyEnvironmentAddOnEnsurePip.SetTriggers( + const Value: TPyEnvironmentaddOnTriggers); +begin + inherited SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); +end; + +constructor TPyEnvironmentAddOnEnsurePip.Create(AOwner: TComponent); +begin + SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); + inherited; +end; + +function TPyEnvironmentAddOnEnsurePip.GetInfo: TPyPluginInfo; +begin + Result.Name := 'ensurepip'; + Result.Description := + 'Provides support for bootstrapping the pip installer into an existing ' + + 'Python installation or virtual environment.' + + sLineBreak + + 'See more: https://pip.pypa.io/en/stable/installation/#ensurepip'; + Result.InstallsWhen := [TPyPluginEvent.AfterActivate]; +end; + +function TPyEnvironmentAddOnEnsurePip.IsInstalled: boolean; +var + LPythonHome: string; + LExecutable: string; + LSharedLibrary: string; + LCmd: IExecCmd; +begin + LPythonHome := GetPythonEngine().PythonHome; + LExecutable := GetPythonEngine().ProgramName; + LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, + GetPythonEngine().DllName); + + LCmd := TExecCmdService.Cmd( + LExecutable, + TExecCmdArgs.BuildArgv( + LExecutable, ['-m', 'pip', '--version']), + TExecCmdArgs.BuildEnvp( + LPythonHome, + LExecutable, + LSharedLibrary) + ).Run(); + + Result := (LCmd.Wait() = EXIT_SUCCESS); +end; + +procedure TPyEnvironmentAddOnEnsurePip.InternalExecute( + const ACancelation: ICancelation); +var + LPythonHome: string; + LExecutable: string; + LSharedLibrary: string; + LInput: TArray; + LCmd: IExecCmd; +begin + inherited; + LPythonHome := GetPythonEngine().PythonHome; + LExecutable := GetPythonEngine().ProgramName; + LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, + GetPythonEngine().DllName); + + LInput := ['-m', 'ensurepip']; + + if FVerbose then + LInput := LInput + ['--verbose']; + + if FUpgrade then + LInput := LInput + ['--upgrade']; + + LCmd := TExecCmdService.Cmd( + LExecutable, + TExecCmdArgs.BuildArgv( + LExecutable, LInput), + TExecCmdArgs.BuildEnvp( + LPythonHome, + LExecutable, + LSharedLibrary)) + .Run([TRedirect.stdout, TRedirect.stderr]); + + TSpinWait.SpinUntil(function(): boolean begin + Result := not LCmd.IsAlive or ACancelation.IsCancelled; + end); + + ACancelation.CheckCancelled(); + + if (LCmd.Wait() <> EXIT_SUCCESS) then + raise EPipSetupFailed.Create('PIP setup has failed.' + + #13#10 + + LCmd.Output); +end; + +end. diff --git a/src/AddOn/PyEnvironment.AddOn.GetPip.pas b/src/AddOn/PyEnvironment.AddOn.GetPip.pas index 1089da9..c28b56f 100644 --- a/src/AddOn/PyEnvironment.AddOn.GetPip.pas +++ b/src/AddOn/PyEnvironment.AddOn.GetPip.pas @@ -1,184 +1,184 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.AddOn.GetPip' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment GetPip add-on *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.AddOn.GetPip; - -interface - -uses - System.SysUtils, - System.Classes, - PyTools.Cancelation, - PyEnvironment, - PyEnvironment.AddOn; - -type - [ComponentPlatforms(pidAllPlatforms)] - TPyEnvironmentAddOnGetPip = class(TPyEnvironmentCustomAddOn) - protected - function GetInfo(): TPyPluginInfo; override; - function IsInstalled(): boolean; override; - procedure SetTriggers(const Value: TPyEnvironmentaddOnTriggers); override; - procedure InternalExecute(const ACancelation: ICancelation); override; - public - constructor Create(AOwner: TComponent); override; - published - property Triggers default [TPyEnvironmentaddOnTrigger.trAfterSetup]; - end; - -implementation - -uses - System.Types, - System.IOUtils, - System.SyncObjs, - PythonEngine, - PyTools.ExecCmd, - PyTools.ExecCmd.Args, - PyEnvironment.Exception; - -{$R ..\..\resources\getpipscript.res} - -{ TPyEnvironmentAddOnGetPip } - -procedure TPyEnvironmentAddOnGetPip.SetTriggers( - const Value: TPyEnvironmentaddOnTriggers); -begin - inherited SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); -end; - -constructor TPyEnvironmentAddOnGetPip.Create(AOwner: TComponent); -begin - SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); - inherited; -end; - -function TPyEnvironmentAddOnGetPip.GetInfo: TPyPluginInfo; -begin - Result.Name := 'get-pip.py'; - Result.Description := - 'Python script that uses some bootstrapping logic to install pip.' - + sLineBreak - + 'See more: https://pip.pypa.io/en/stable/installation/#get-pip-py'; - Result.InstallsWhen := [TPyPluginEvent.AfterActivate]; -end; - -function TPyEnvironmentAddOnGetPip.IsInstalled: boolean; -var - LPythonHome: string; - LExecutable: string; - LSharedLibrary: string; - LCmd: IExecCmd; -begin - LPythonHome := GetPythonEngine().PythonHome; - LExecutable := GetPythonEngine().ProgramName; - LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, - GetPythonEngine().DllName); - - // Check if pip is installed - LCmd := TExecCmdService.Cmd( - LExecutable, - TExecCmdArgs.BuildArgv( - LExecutable, ['-m', 'pip', '--version']), - TExecCmdArgs.BuildEnvp( - LPythonHome, - LExecutable, - LSharedLibrary) - ).Run(); - - Result := (LCmd.Wait() = EXIT_SUCCESS); -end; - -procedure TPyEnvironmentAddOnGetPip.InternalExecute( - const ACancelation: ICancelation); -var - LPythonHome: string; - LExecutable: string; - LSharedLibrary: string; - LResStream: TResourceStream; - LFileName: string; - LPths: TArray; - LStrings: TStringList; - I: Integer; - LCmd: IExecCmd; -begin - inherited; - LPythonHome := GetPythonEngine().PythonHome; - LExecutable := GetPythonEngine().ProgramName; - LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, - GetPythonEngine().DllName); - - LPths := TDirectory.GetFiles( - LPythonHome, 'python*._pth', TSearchOption.soTopDirectoryOnly); - if (Length(LPths) > 0) then begin - LStrings := TStringList.Create(); - try - LStrings.LoadFromFile(LPths[0]); - for I := 0 to LStrings.Count -1 do - if LStrings[I].Trim().StartsWith('#import site') then - LStrings[I] := 'import site'; - LStrings.SaveToFile(LPths[0]); - finally - LStrings.Free(); - end; - end; - - ACancelation.CheckCancelled(); - - //Run the get-pip.py script to enabled PIP - LFileName := TPath.GetTempFileName(); - LResStream := TResourceStream.Create(HInstance, 'getpippy', RT_RCDATA); - try - LResStream.SaveToFile(LFileName); - LCmd := TExecCmdService.Cmd( - LExecutable, - TExecCmdArgs.BuildArgv( - LExecutable, [LFileName]), - TExecCmdArgs.BuildEnvp( - LPythonHome, - LExecutable, - LSharedLibrary)) - .Run([TRedirect.stderr]); - - TSpinWait.SpinUntil(function(): boolean begin - Result := not LCmd.IsAlive or ACancelation.IsCancelled; - end); - - ACancelation.CheckCancelled(); - - if (LCmd.Wait() <> EXIT_SUCCESS) then - raise EPipSetupFailed.Create('PIP setup has failed.' + #13#10 + LCmd.StdErr.ReadAll()); - finally - LResStream.Free(); - end; -end; - -end. - +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.AddOn.GetPip' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment GetPip add-on *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.AddOn.GetPip; + +interface + +uses + System.SysUtils, + System.Classes, + PyTools.Cancelation, + PyEnvironment, + PyEnvironment.AddOn; + +type + [ComponentPlatforms(pidAllPlatforms)] + TPyEnvironmentAddOnGetPip = class(TPyEnvironmentCustomAddOn) + protected + function GetInfo(): TPyPluginInfo; override; + function IsInstalled(): boolean; override; + procedure SetTriggers(const Value: TPyEnvironmentaddOnTriggers); override; + procedure InternalExecute(const ACancelation: ICancelation); override; + public + constructor Create(AOwner: TComponent); override; + published + property Triggers default [TPyEnvironmentaddOnTrigger.trAfterSetup]; + end; + +implementation + +uses + System.Types, + System.IOUtils, + System.SyncObjs, + PythonEngine, + PyTools.ExecCmd, + PyTools.ExecCmd.Args, + PyEnvironment.Exception; + +{$R ..\..\resources\getpipscript.res} + +{ TPyEnvironmentAddOnGetPip } + +procedure TPyEnvironmentAddOnGetPip.SetTriggers( + const Value: TPyEnvironmentaddOnTriggers); +begin + inherited SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); +end; + +constructor TPyEnvironmentAddOnGetPip.Create(AOwner: TComponent); +begin + SetTriggers([TPyEnvironmentaddOnTrigger.trAfterSetup]); + inherited; +end; + +function TPyEnvironmentAddOnGetPip.GetInfo: TPyPluginInfo; +begin + Result.Name := 'get-pip.py'; + Result.Description := + 'Python script that uses some bootstrapping logic to install pip.' + + sLineBreak + + 'See more: https://pip.pypa.io/en/stable/installation/#get-pip-py'; + Result.InstallsWhen := [TPyPluginEvent.AfterActivate]; +end; + +function TPyEnvironmentAddOnGetPip.IsInstalled: boolean; +var + LPythonHome: string; + LExecutable: string; + LSharedLibrary: string; + LCmd: IExecCmd; +begin + LPythonHome := GetPythonEngine().PythonHome; + LExecutable := GetPythonEngine().ProgramName; + LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, + GetPythonEngine().DllName); + + // Check if pip is installed + LCmd := TExecCmdService.Cmd( + LExecutable, + TExecCmdArgs.BuildArgv( + LExecutable, ['-m', 'pip', '--version']), + TExecCmdArgs.BuildEnvp( + LPythonHome, + LExecutable, + LSharedLibrary) + ).Run(); + + Result := (LCmd.Wait() = EXIT_SUCCESS); +end; + +procedure TPyEnvironmentAddOnGetPip.InternalExecute( + const ACancelation: ICancelation); +var + LPythonHome: string; + LExecutable: string; + LSharedLibrary: string; + LResStream: TResourceStream; + LFileName: string; + LPths: TArray; + LStrings: TStringList; + I: Integer; + LCmd: IExecCmd; +begin + inherited; + LPythonHome := GetPythonEngine().PythonHome; + LExecutable := GetPythonEngine().ProgramName; + LSharedLibrary := TPath.Combine(GetPythonEngine().DllPath, + GetPythonEngine().DllName); + + LPths := TDirectory.GetFiles( + LPythonHome, 'python*._pth', TSearchOption.soTopDirectoryOnly); + if (Length(LPths) > 0) then begin + LStrings := TStringList.Create(); + try + LStrings.LoadFromFile(LPths[0]); + for I := 0 to LStrings.Count -1 do + if LStrings[I].Trim().StartsWith('#import site') then + LStrings[I] := 'import site'; + LStrings.SaveToFile(LPths[0]); + finally + LStrings.Free(); + end; + end; + + ACancelation.CheckCancelled(); + + //Run the get-pip.py script to enabled PIP + LFileName := TPath.GetTempFileName(); + LResStream := TResourceStream.Create(HInstance, 'getpippy', RT_RCDATA); + try + LResStream.SaveToFile(LFileName); + LCmd := TExecCmdService.Cmd( + LExecutable, + TExecCmdArgs.BuildArgv( + LExecutable, [LFileName]), + TExecCmdArgs.BuildEnvp( + LPythonHome, + LExecutable, + LSharedLibrary)) + .Run([TRedirect.stderr]); + + TSpinWait.SpinUntil(function(): boolean begin + Result := not LCmd.IsAlive or ACancelation.IsCancelled; + end); + + ACancelation.CheckCancelled(); + + if (LCmd.Wait() <> EXIT_SUCCESS) then + raise EPipSetupFailed.Create('PIP setup has failed.' + #13#10 + LCmd.StdErr.ReadAll()); + finally + LResStream.Free(); + end; +end; + +end. + diff --git a/src/AddOn/PyEnvironment.AddOn.pas b/src/AddOn/PyEnvironment.AddOn.pas index 4647abf..4fdfe83 100644 --- a/src/AddOn/PyEnvironment.AddOn.pas +++ b/src/AddOn/PyEnvironment.AddOn.pas @@ -1,239 +1,239 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.AddOn' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment AddOn *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.AddOn; - -interface - -uses - System.Classes, - System.Generics.Collections, - System.SysUtils, - System.Types, - System.Rtti, - System.Threading, - PyTools.Cancelation, - PyEnvironment; - -type - TPyEnvironmentCustomAddOn = class; - - TPyEnvironmentaddOnTrigger = ( - trBeforeSetup, trAfterSetup, - trBeforeActivate, trAfterActivate, - trBeforeDeactivate, trAfterDeactivate); - - TPyEnvironmentaddOnTriggers = set of TPyEnvironmentaddOnTrigger; - - TPyEnvironmentAddOnExecute = procedure(const ASender: TObject) of object; - - TPyEnvironmentAddOnExecuteError = procedure(const ASender: TObject; - const AException: Exception; var AAbort: boolean) of object; - - TPyEnvironmentCustomAddOn = class(TComponent, IPyEnvironmentPlugin) - private type - TPyEnvironmentCustomAddonAsyncResult = class(TBaseAsyncResult) - private - FAsyncTask: TProc; - FCancelation: ICancelation; - protected - procedure AsyncDispatch; override; - procedure Schedule; override; - function DoCancel: Boolean; override; - public - constructor Create(const AContext: TObject; const AAsyncTask: TProc); - end; - private - FEnvironment: TPyCustomEnvironment; - FOnExecute: TPyEnvironmentAddOnExecute; - FTriggers: TPyEnvironmentaddOnTriggers; - FOnExecuteError: TPyEnvironmentAddOnExecuteError; - procedure SetEnvironment(const Value: TPyCustomEnvironment); - procedure DoOnExecute(); - procedure DoInternalError(); - //IPlugin implementation - procedure InstallPlugin(const ACancelation: ICancelation); - procedure UninstallPlugin(const ACancelation: ICancelation); - procedure LoadPlugin(const ACancelation: ICancelation); - procedure UnloadPlugin(const ACancelation: ICancelation); - protected - procedure Notification(AComponent: TComponent; AOperation: TOperation); override; - protected - //Getters and Setters - function GetInfo(): TPyPluginInfo; virtual; abstract; - procedure SetTriggers(const Value: TPyEnvironmentaddOnTriggers); virtual; - protected - procedure InternalExecute(const ACancelation: ICancelation); virtual; abstract; - function IsInstalled(): boolean; virtual; abstract; - public - destructor Destroy(); override; - - procedure Execute(const ACancelation: ICancelation); - published - property Environment: TPyCustomEnvironment read FEnvironment write SetEnvironment; - property Triggers: TPyEnvironmentaddOnTriggers read FTriggers write SetTriggers; - property OnExecute: TPyEnvironmentAddOnExecute read FOnExecute write FOnExecute; - property OnExecuteError: TPyEnvironmentAddOnExecuteError read FOnExecuteError write FOnExecuteError; - end; - - [ComponentPlatforms(pidAllPlatforms)] - TPyEnvironmentAddOn = class(TPyEnvironmentCustomAddOn); - -implementation - -{ TPyEnvironmentCustomAddOn } - -procedure TPyEnvironmentCustomAddOn.SetTriggers( - const Value: TPyEnvironmentaddOnTriggers); -begin - FTriggers := Value; -end; - -procedure TPyEnvironmentCustomAddOn.SetEnvironment( - const Value: TPyCustomEnvironment); -begin - if Assigned(FEnvironment) then begin - FEnvironment.RemoveFreeNotification(Self); - if not (csDesigning in ComponentState) then - FEnvironment.RemovePlugin(Self); - end; - - FEnvironment := Value; - if Assigned(FEnvironment) then begin - FEnvironment.FreeNotification(Self); - if not (csDesigning in ComponentState) then - FEnvironment.AddPlugin(Self); - end; -end; - -procedure TPyEnvironmentCustomAddOn.Notification(AComponent: TComponent; - AOperation: TOperation); -begin - inherited; - if (AOperation = opRemove) and (AComponent = FEnvironment) then begin - SetEnvironment(nil); - end; -end; - -destructor TPyEnvironmentCustomAddOn.Destroy; -begin - SetEnvironment(nil); - inherited; -end; - -procedure TPyEnvironmentCustomAddOn.DoInternalError; -var - LAbort: boolean; - LException: Exception; -begin - if Assigned(FOnExecuteError) then begin - LException := Exception(ExceptObject()); - TThread.Synchronize(nil, procedure() begin - FOnExecuteError(Self, LException, LAbort); - end); - - if LAbort then - Abort(); - end else begin - try - raise Exception(AcquireExceptionObject()) at ExceptAddr(); - finally - ReleaseExceptionObject(); - end; - end; -end; - -procedure TPyEnvironmentCustomAddOn.DoOnExecute; -begin - if Assigned(FOnExecute) then - TThread.Synchronize(nil, procedure() begin - FOnExecute(Self); - end); -end; - -procedure TPyEnvironmentCustomAddOn.Execute(const ACancelation: ICancelation); -begin - Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); - - DoOnExecute(); - try - InternalExecute(ACancelation); - except - DoInternalError(); - end; -end; - -procedure TPyEnvironmentCustomAddOn.InstallPlugin(const ACancelation: ICancelation); -begin - Execute(ACancelation); -end; - -procedure TPyEnvironmentCustomAddOn.UninstallPlugin(const ACancelation: ICancelation); -begin - // -end; - -procedure TPyEnvironmentCustomAddOn.LoadPlugin(const ACancelation: ICancelation); -begin - // -end; - -procedure TPyEnvironmentCustomAddOn.UnloadPlugin(const ACancelation: ICancelation); -begin - // -end; - -{ TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult } - -constructor TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.Create( - const AContext: TObject; const AAsyncTask: TProc); -begin - inherited Create(AContext); - FAsyncTask := AAsyncTask; - FCancelation := TCancelation.Create(); -end; - -function TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.DoCancel: Boolean; -begin - FCancelation.Cancel(); - Result := true; -end; - -procedure TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.AsyncDispatch; -begin - FAsyncTask(FCancelation); -end; - -procedure TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.Schedule; -begin - TTask.Run(DoAsyncDispatch); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.AddOn' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment AddOn *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.AddOn; + +interface + +uses + System.Classes, + System.Generics.Collections, + System.SysUtils, + System.Types, + System.Rtti, + System.Threading, + PyTools.Cancelation, + PyEnvironment; + +type + TPyEnvironmentCustomAddOn = class; + + TPyEnvironmentaddOnTrigger = ( + trBeforeSetup, trAfterSetup, + trBeforeActivate, trAfterActivate, + trBeforeDeactivate, trAfterDeactivate); + + TPyEnvironmentaddOnTriggers = set of TPyEnvironmentaddOnTrigger; + + TPyEnvironmentAddOnExecute = procedure(const ASender: TObject) of object; + + TPyEnvironmentAddOnExecuteError = procedure(const ASender: TObject; + const AException: Exception; var AAbort: boolean) of object; + + TPyEnvironmentCustomAddOn = class(TComponent, IPyEnvironmentPlugin) + private type + TPyEnvironmentCustomAddonAsyncResult = class(TBaseAsyncResult) + private + FAsyncTask: TProc; + FCancelation: ICancelation; + protected + procedure AsyncDispatch; override; + procedure Schedule; override; + function DoCancel: Boolean; override; + public + constructor Create(const AContext: TObject; const AAsyncTask: TProc); + end; + private + FEnvironment: TPyCustomEnvironment; + FOnExecute: TPyEnvironmentAddOnExecute; + FTriggers: TPyEnvironmentaddOnTriggers; + FOnExecuteError: TPyEnvironmentAddOnExecuteError; + procedure SetEnvironment(const Value: TPyCustomEnvironment); + procedure DoOnExecute(); + procedure DoInternalError(); + //IPlugin implementation + procedure InstallPlugin(const ACancelation: ICancelation); + procedure UninstallPlugin(const ACancelation: ICancelation); + procedure LoadPlugin(const ACancelation: ICancelation); + procedure UnloadPlugin(const ACancelation: ICancelation); + protected + procedure Notification(AComponent: TComponent; AOperation: TOperation); override; + protected + //Getters and Setters + function GetInfo(): TPyPluginInfo; virtual; abstract; + procedure SetTriggers(const Value: TPyEnvironmentaddOnTriggers); virtual; + protected + procedure InternalExecute(const ACancelation: ICancelation); virtual; abstract; + function IsInstalled(): boolean; virtual; abstract; + public + destructor Destroy(); override; + + procedure Execute(const ACancelation: ICancelation); + published + property Environment: TPyCustomEnvironment read FEnvironment write SetEnvironment; + property Triggers: TPyEnvironmentaddOnTriggers read FTriggers write SetTriggers; + property OnExecute: TPyEnvironmentAddOnExecute read FOnExecute write FOnExecute; + property OnExecuteError: TPyEnvironmentAddOnExecuteError read FOnExecuteError write FOnExecuteError; + end; + + [ComponentPlatforms(pidAllPlatforms)] + TPyEnvironmentAddOn = class(TPyEnvironmentCustomAddOn); + +implementation + +{ TPyEnvironmentCustomAddOn } + +procedure TPyEnvironmentCustomAddOn.SetTriggers( + const Value: TPyEnvironmentaddOnTriggers); +begin + FTriggers := Value; +end; + +procedure TPyEnvironmentCustomAddOn.SetEnvironment( + const Value: TPyCustomEnvironment); +begin + if Assigned(FEnvironment) then begin + FEnvironment.RemoveFreeNotification(Self); + if not (csDesigning in ComponentState) then + FEnvironment.RemovePlugin(Self); + end; + + FEnvironment := Value; + if Assigned(FEnvironment) then begin + FEnvironment.FreeNotification(Self); + if not (csDesigning in ComponentState) then + FEnvironment.AddPlugin(Self); + end; +end; + +procedure TPyEnvironmentCustomAddOn.Notification(AComponent: TComponent; + AOperation: TOperation); +begin + inherited; + if (AOperation = opRemove) and (AComponent = FEnvironment) then begin + SetEnvironment(nil); + end; +end; + +destructor TPyEnvironmentCustomAddOn.Destroy; +begin + SetEnvironment(nil); + inherited; +end; + +procedure TPyEnvironmentCustomAddOn.DoInternalError; +var + LAbort: boolean; + LException: Exception; +begin + if Assigned(FOnExecuteError) then begin + LException := Exception(ExceptObject()); + TThread.Synchronize(nil, procedure() begin + FOnExecuteError(Self, LException, LAbort); + end); + + if LAbort then + Abort(); + end else begin + try + raise Exception(AcquireExceptionObject()) at ExceptAddr(); + finally + ReleaseExceptionObject(); + end; + end; +end; + +procedure TPyEnvironmentCustomAddOn.DoOnExecute; +begin + if Assigned(FOnExecute) then + TThread.Synchronize(nil, procedure() begin + FOnExecute(Self); + end); +end; + +procedure TPyEnvironmentCustomAddOn.Execute(const ACancelation: ICancelation); +begin + Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); + + DoOnExecute(); + try + InternalExecute(ACancelation); + except + DoInternalError(); + end; +end; + +procedure TPyEnvironmentCustomAddOn.InstallPlugin(const ACancelation: ICancelation); +begin + Execute(ACancelation); +end; + +procedure TPyEnvironmentCustomAddOn.UninstallPlugin(const ACancelation: ICancelation); +begin + // +end; + +procedure TPyEnvironmentCustomAddOn.LoadPlugin(const ACancelation: ICancelation); +begin + // +end; + +procedure TPyEnvironmentCustomAddOn.UnloadPlugin(const ACancelation: ICancelation); +begin + // +end; + +{ TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult } + +constructor TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.Create( + const AContext: TObject; const AAsyncTask: TProc); +begin + inherited Create(AContext); + FAsyncTask := AAsyncTask; + FCancelation := TCancelation.Create(); +end; + +function TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.DoCancel: Boolean; +begin + FCancelation.Cancel(); + Result := true; +end; + +procedure TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.AsyncDispatch; +begin + FAsyncTask(FCancelation); +end; + +procedure TPyEnvironmentCustomAddOn.TPyEnvironmentCustomAddonAsyncResult.Schedule; +begin + TTask.Run(DoAsyncDispatch); +end; + +end. diff --git a/src/Embeddable/PyEnvironment.Embeddable.pas b/src/Embeddable/PyEnvironment.Embeddable.pas index 7348ae4..7d268c6 100644 --- a/src/Embeddable/PyEnvironment.Embeddable.pas +++ b/src/Embeddable/PyEnvironment.Embeddable.pas @@ -1,579 +1,599 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Embeddable' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment embeddable *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Embeddable; - -interface - -uses - System.Classes, - System.SysUtils, - System.Zip, - PythonEngine, - PyTools.Cancelation, - PyEnvironment, - PyEnvironment.Distribution; - -type - (*-----------------------------------------------------------------------*) - (* *) - (* Embeddables structure example *) - (* *) - (* [Root] Directory *) - (* +-- python version/ *) - (* +-- python zip *) - (*-----------------------------------------------------------------------*) - - TPyCustomEmbeddableDistribution = class; - TZipProgress = procedure(Sender: TObject; - ADistribution: TPyCustomEmbeddableDistribution; FileName: string; - Header: TZipHeader; Position: Int64) of object; - - TPyCustomEmbeddableDistribution = class(TPyDistribution) - private - FEmbeddablePackage: string; - FEnvironmentPath: string; - FOnZipProgress: TZipProgress; - function FindSharedLibrary(): string; - function FindExecutable(): string; - private - procedure DoZipProgressEvt(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64); - {$IFDEF POSIX} - function FileIsExecutable(const AFilePath: string): boolean; - {$ENDIF POSIX} - procedure CreateSymlink(const ASymlink, ATarget: string); - protected - function EnvironmentExists(): boolean; - /// - /// Navigates through the embeddables searching for a compatible distribution. - /// - function EmbeddableExists(): boolean; - /// - /// Creates a new environment based on the current settings. - /// An embeddable distribution will be used as an "image". - /// - procedure CreateEnvironment(const ACancelation: ICancelation); virtual; - procedure CreateSymLinks(); virtual; - procedure LoadSettings(const ACancelation: ICancelation); virtual; - protected - function GetEnvironmentPath(): string; - public - function Setup(const ACancelation: ICancelation): boolean; override; - public - property EmbeddablePackage: string read FEmbeddablePackage write FEmbeddablePackage; - published - property EnvironmentPath: string read FEnvironmentPath write FEnvironmentPath; - property OnZipProgress: TZipProgress read FOnZipProgress write FOnZipProgress; - end; - - TPyEmbeddableDistribution = class(TPyCustomEmbeddableDistribution) - private - FScanned: boolean; - FDeleteEmbeddable: boolean; - procedure DoDeleteEmbeddable(); - protected - procedure LoadSettings(const ACancelation: ICancelation); override; - public - function Setup(const ACancelation: ICancelation): boolean; override; - property Scanned: boolean read FScanned write FScanned; - published - property EmbeddablePackage; - /// - /// Delete the embeddable zip file after install. - /// - property DeleteEmbeddable: boolean read FDeleteEmbeddable write FDeleteEmbeddable; - end; - - TPyEmbeddableCustomCollection = class(TPyDistributionCollection); - - TPyEmbeddableCollection = class(TPyEmbeddableCustomCollection); - - TPyCustomEmbeddedEnvironment = class(TPyEnvironment) - private - FOnZipProgress: TZipProgress; - published - property OnZipProgress: TZipProgress read FOnZipProgress write FOnZipProgress; - end; - - [ComponentPlatforms(pidAllPlatforms)] - TPyEmbeddedEnvironment = class(TPyCustomEmbeddedEnvironment) - public type - TScanRule = (srFolder, srFileName); - private type - TScanner = class(TPersistent) - private - FAutoScan: boolean; - FScanRule: TScanRule; - FEmbeddablesPath: string; - FEnvironmentPath: string; - FDeleteEmbeddable: boolean; - public - procedure Scan(const AEmbedabblesPath: string; ACallback: TProc); - published - property AutoScan: boolean read FAutoScan write FAutoScan default false; - property ScanRule: TScanRule read FScanRule write FScanRule; - property EmbeddablesPath: string read FEmbeddablesPath write FEmbeddablesPath; - /// - /// Default environment path. - /// - property EnvironmentPath: string read FEnvironmentPath write FEnvironmentPath; - /// - /// Delete the embeddable zip file after install. - /// - property DeleteEmbeddable: boolean read FDeleteEmbeddable write FDeleteEmbeddable; - end; - private - FScanner: TScanner; - procedure SetScanner(const Value: TScanner); - protected - function CreateCollection(): TPyDistributionCollection; override; - procedure Prepare(const ACancelation: ICancelation); override; - public - constructor Create(AOwner: TComponent); override; - destructor Destroy(); override; - published - property Distributions; - property Scanner: TScanner read FScanner write SetScanner; - end; - -implementation - -uses - System.IOUtils, - System.Character, - System.StrUtils, - System.RegularExpressions, - PyTools.ExecCmd, - PyEnvironment.Exception, - PyEnvironment.Path - {$IFDEF POSIX} - , Posix.SysStat, Posix.Stdlib, Posix.String_, Posix.Errno, Posix.Unistd - {$ENDIF} - ; - -type - TZipEventToAnonMethodAdapter = class - public type - TAdapterProc = TProc; - private - FAdapterProc: TAdapterProc; - public - constructor Create(const AAdapterProc: TAdapterProc); - procedure Evt(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64); - end; - -{ TPyCustomEmbeddableDistribution } - -procedure TPyCustomEmbeddableDistribution.CreateEnvironment(const ACancelation: ICancelation); -var - LAdapter: TZipEventToAnonMethodAdapter; - LProgress: TZipEventToAnonMethodAdapter.TAdapterProc; -begin - LProgress := procedure(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64) - begin - ACancelation.CheckCancelled(); - DoZipProgressEvt(Sender, FileName, Header, Position); - end; - - LAdapter := TZipEventToAnonMethodAdapter.Create(LProgress); - try - TZipFile.ExtractZipFile(FEmbeddablePackage, GetEnvironmentPath(), LAdapter.Evt); - finally - LAdapter.Free(); - end; -end; - -procedure TPyCustomEmbeddableDistribution.CreateSymlink(const ASymlink, - ATarget: string); -var - LExistingTarget: string; -begin - LExistingTarget := String.Empty; - if TFile.Exists(ATarget) then begin - //There is a bug with TFile.Exists and TFile.GetSymLinkTarget for Android - //So we recreate it every time - DeleteFile(ASymlink); - - if not TFile.CreateSymLink(ASymlink, ATarget) then - raise ESymlinkFailed.CreateFmt('Failed to create the symlink: %s -> %s', [ASymlink, ATarget]); - end; -end; - -procedure TPyCustomEmbeddableDistribution.CreateSymLinks; -var - LTargetInterpreter: string; - LTargetLauncher: string; - LSymlinkInterpreter: string; - LSynlinkLauncher: string; -begin - {$IFNDEF ANDROID} - Exit; - {$ENDIF ANDROID} - //Real file names - LTargetInterpreter := TPath.Combine( - TPath.GetLibraryPath(), - String.Format('libpython%s.so', [PythonVersion])); - LTargetLauncher := TPath.Combine( - TPath.GetLibraryPath(), - String.Format('libpythonlauncher%s.so', [PythonVersion])); - //Symlink names - LSymlinkInterpreter := TPath.Combine( - TPath.Combine(GetEnvironmentPath(), 'lib'), - String.Format('libpython%s.so', [PythonVersion])); - LSynlinkLauncher := TPath.Combine( - TPath.Combine(GetEnvironmentPath(), 'bin'), - String.Format('python%s', [PythonVersion])); - //Creates the interpreter symlink - CreateSymlink(LSymlinkInterpreter, LTargetInterpreter); - //Creates the launcher symlink - CreateSymlink(LSynlinkLauncher, LTargetLauncher); -end; - -procedure TPyCustomEmbeddableDistribution.DoZipProgressEvt(Sender: TObject; FileName: string; - Header: TZipHeader; Position: Int64); -begin - if Assigned(FOnZipProgress) then - FOnZipProgress(Sender, Self, FileName, Header, Position); -end; - -function TPyCustomEmbeddableDistribution.EmbeddableExists: boolean; -begin - Result := TFile.Exists(FEmbeddablePackage); -end; - -function TPyCustomEmbeddableDistribution.EnvironmentExists: boolean; -begin - Result := TDirectory.Exists(GetEnvironmentPath()); -end; - -{$IFDEF POSIX} -function TPyCustomEmbeddableDistribution.FileIsExecutable( - const AFilePath: string): boolean; -begin - {$WARN SYMBOL_PLATFORM OFF} - //Avoiding symlinks - Result := (TFileAttribute.faOwnerExecute in TFile.GetAttributes(AFilePath)) - or (TFileAttribute.faGroupExecute in TFile.GetAttributes(AFilePath)) - or (TFileAttribute.faOthersExecute in TFile.GetAttributes(AFilePath)); - {$WARN SYMBOL_PLATFORM ON} -end; -{$ENDIF POSIX} - -function TPyCustomEmbeddableDistribution.FindExecutable: string; - - function DoSearch(const APath: string): TArray; - {$IFDEF POSIX} - var - LFile: string; - {$ENDIF POSIX} - begin - Result := TDirectory.GetFiles(APath, 'python*', TSearchOption.soTopDirectoryOnly, - function(const Path: string; const SearchRec: TSearchRec): boolean - var - LFileName: string; - begin - LFileName := SearchRec.Name; - if LFileName.EndsWith('m') then //3.7 and lower contain a "m" as sufix. - LFileName := LFileName.Remove(Length(LFileName) - 1); - - Result := Char.IsDigit(LFileName, Length(LFileName) - 1); - end); - - {$IFDEF POSIX} - for LFile in Result do begin - if (TPath.GetFileName(LFile).StartsWith('python' + PythonVersion)) and (FileIsExecutable(LFile)) then - Exit(TArray.Create(LFile)); - end; - {$ENDIF POSIX} - end; - -var - LFiles: TArray; -begin - {$IFDEF MSWINDOWS} - //If we get this far and we're in a Windows only section - //then we're done so just exit with the Result intact - Result := TPath.Combine(GetEnvironmentPath(), 'python.exe'); - if TFile.Exists(Result) then - Exit(Result) - else - Exit(String.Empty); - {$ELSE} - Result := TPath.Combine(GetEnvironmentPath(), 'bin'); - {$ENDIF} - - LFiles := DoSearch(Result); - if Length(LFiles) > 0 then begin - Result := LFiles[Low(LFiles)]; - if not TFile.Exists(Result) then - Result := String.Empty; - end else - Result := String.Empty; -end; - -function TPyCustomEmbeddableDistribution.FindSharedLibrary: string; - - function DoSearch(const ALibName: string; const APath: string): TArray; - var - LFile: string; - LSearch: string; - begin - LFile := TPath.Combine(APath, ALibName); - if TFile.Exists(LFile) then - Exit(TArray.Create(LFile)); - - LSearch := ALibName.Replace(TPath.GetExtension(ALibName), '') + '*' + TPath.GetExtension(ALibName); - Result := TDirectory.GetFiles( - APath, - LSearch, //Python <= 3.7 might contain a "m" as sufix. - TSearchOption.soTopDirectoryOnly); - end; - -var - I: integer; - LLibName: string; - LPath: string; - LFiles: TArray; -begin - for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do - if PythonVersion.StartsWith(PYTHON_KNOWN_VERSIONS[I].RegVersion) then begin - LLibName := PYTHON_KNOWN_VERSIONS[I].DllName; - Break; - end; - - {$IFDEF MSWINDOWS} - LPath := GetEnvironmentPath(); - {$ELSEIF DEFINED(MACOS)} - //Let's try it in the library path first - LPath := TPyEnvironmentPath.ResolvePath(TPyEnvironmentPath.ENVIRONMENT_PATH); - LFiles := DoSearch(LLibName, LPath); - if LFiles <> nil then - Exit(LFiles[Low(LFiles)]); - //Try to find it in the environment folder - LPath := TPath.Combine(GetEnvironmentPath(), 'lib'); - {$ELSE} - LPath := TPath.Combine(GetEnvironmentPath(), 'lib'); - {$ENDIF} - - LFiles := DoSearch(LLibName, LPath); - if LFiles <> nil then - Result := LFiles[Low(LFiles)] - else - Result := String.Empty; - - {$IFDEF LINUX} - //Targets directly to the so file instead of a symlink. - if TFile.Exists(Result + '.1.0') then - Result := Result + '.1.0'; - {$ENDIF} -end; - -procedure TPyCustomEmbeddableDistribution.LoadSettings(const ACancelation: ICancelation); -begin - ACancelation.CheckCancelled(); - - Home := GetEnvironmentPath(); - SharedLibrary := FindSharedLibrary(); - Executable := FindExecutable(); -end; - -function TPyCustomEmbeddableDistribution.GetEnvironmentPath: string; -begin - Result := TPyEnvironmentPath.ResolvePath(EnvironmentPath, PythonVersion); -end; - -function TPyCustomEmbeddableDistribution.Setup(const ACancelation: ICancelation): boolean; -begin - inherited; - if not EnvironmentExists() then begin - if not EmbeddableExists() then - raise EEmbeddableNotFound.CreateFmt( - 'Embeddable package not found.' + #13#10 + '%s', [FEmbeddablePackage]); - - CreateEnvironment(ACancelation); - end; - - CreateSymLinks(); - - LoadSettings(ACancelation); - - Result := true; -end; - -{ TPyEmbeddableDistribution } - -procedure TPyEmbeddableDistribution.DoDeleteEmbeddable; -begin - TFile.Delete(EmbeddablePackage); -end; - -procedure TPyEmbeddableDistribution.LoadSettings(const ACancelation: ICancelation); -begin - if FScanned then - inherited; -end; - -function TPyEmbeddableDistribution.Setup(const ACancelation: ICancelation): boolean; -begin - Result := inherited; - if FDeleteEmbeddable and EmbeddableExists() then - DoDeleteEmbeddable(); -end; - -{ TPyEmbeddedEnvironment } - -constructor TPyEmbeddedEnvironment.Create(AOwner: TComponent); -begin - inherited; - FScanner := TScanner.Create(); - FScanner.ScanRule := TScanRule.srFolder; - if not (csDesigning in ComponentState) then begin - FScanner.EnvironmentPath := TPyEnvironmentPath.CreateEnvironmentPath(); - FScanner.EmbeddablesPath := TPyEnvironmentPath.CreateEmbeddablesPath(); - end; -end; - -destructor TPyEmbeddedEnvironment.Destroy; -begin - FScanner.Free(); - inherited; -end; - -function TPyEmbeddedEnvironment.CreateCollection: TPyDistributionCollection; -begin - Result := TPyEmbeddableCollection.Create(Self, TPyEmbeddableDistribution); -end; - -procedure TPyEmbeddedEnvironment.Prepare(const ACancelation: ICancelation); -var - I: integer; - LExistingEnvironment: string; - LDistribution: TPyEmbeddableDistribution; -begin - if FScanner.AutoScan then begin - //Let's first look for existing environments - for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin - LExistingEnvironment := TPyEnvironmentPath.ResolvePath( - FScanner.EnvironmentPath, - PYTHON_KNOWN_VERSIONS[I].RegVersion); - - if TDirectory.Exists(LExistingEnvironment) then begin - LDistribution := TPyEmbeddableDistribution(Distributions.Add()); - LDistribution.Scanned := true; - LDistribution.PythonVersion := PYTHON_KNOWN_VERSIONS[I].RegVersion; - LDistribution.EnvironmentPath := LExistingEnvironment; - end; - end; - - FScanner.Scan( - TPyEnvironmentPath.ResolvePath(FScanner.EmbeddablesPath), - procedure(APyVersionInfo: TPythonVersionProp; AEmbeddablePackage: string) begin - if Assigned(Distributions.LocateEnvironment(APyVersionInfo.RegVersion)) then - Exit; - - LDistribution := TPyEmbeddableDistribution(Distributions.Add()); - LDistribution.Scanned := true; - LDistribution.PythonVersion := APyVersionInfo.RegVersion; - LDistribution.EnvironmentPath := TPyEnvironmentPath.ResolvePath( - FScanner.EnvironmentPath, - APyVersionInfo.RegVersion); - LDistribution.EmbeddablePackage := AEmbeddablePackage; - LDistribution.OnZipProgress := FOnZipProgress; - LDistribution.DeleteEmbeddable := FScanner.DeleteEmbeddable; - end); - end; - inherited; -end; - -procedure TPyEmbeddedEnvironment.SetScanner(const Value: TScanner); -begin - FScanner.Assign(Value); -end; - -{ TPyEmbeddedEnvironment.TScanner } - -procedure TPyEmbeddedEnvironment.TScanner.Scan(const AEmbedabblesPath: string; - ACallback: TProc); -var - I: Integer; - LPath: string; - LFiles: TArray; - LPythonVersion: string; - LSearchPatter: string; -begin - if not Assigned(ACallback) then - Exit; - - if not TDirectory.Exists(AEmbedabblesPath) then - raise EDirectoryNotFoundException.CreateFmt('Directory "%s" not found.', [ - AEmbedabblesPath]); - - //Look for version named subfolders - if (FScanRule = TScanRule.srFolder) then begin - LSearchPatter := '*.zip'; - for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin - LPath := TPath.Combine(AEmbedabblesPath, PYTHON_KNOWN_VERSIONS[I].RegVersion); - if not TDirectory.Exists(LPath) then - Continue; - - LFiles := TDirectory.GetFiles(LPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); - if (Length(LFiles) = 0) then - Continue; - - ACallback(PYTHON_KNOWN_VERSIONS[I], LFiles[0]); - end; - end else if (FScanRule = TScanRule.srFileName) then begin - //Look for pattern named files - for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin - LPythonVersion := PYTHON_KNOWN_VERSIONS[I].RegVersion; - LSearchPatter := Format('python3-*-%s*.zip', [LPythonVersion]); - LFiles := TDirectory.GetFiles(AEmbedabblesPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); - if (Length(LFiles) = 0) then - Continue; - - ACallback(PYTHON_KNOWN_VERSIONS[I], LFiles[0]); - end - end; -end; - -{ TZipEventToAnonMethodAdapter } - -constructor TZipEventToAnonMethodAdapter.Create(const AAdapterProc: TAdapterProc); -begin - FAdapterProc := AAdapterProc; -end; - -procedure TZipEventToAnonMethodAdapter.Evt(Sender: TObject; FileName: string; - Header: TZipHeader; Position: Int64); -begin - FAdapterProc(Sender, FileName, Header, Position); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Embeddable' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment embeddable *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Embeddable; + +interface + +uses + System.Classes, + System.SysUtils, + System.Zip, + PythonEngine, + PyTools.Cancelation, + PyEnvironment, + PyEnvironment.Distribution; + +type + (*-----------------------------------------------------------------------*) + (* *) + (* Embeddables structure example *) + (* *) + (* [Root] Directory *) + (* +-- python version/ *) + (* +-- python zip *) + (*-----------------------------------------------------------------------*) + + TPyCustomEmbeddableDistribution = class; + TZipProgress = procedure(Sender: TObject; + ADistribution: TPyCustomEmbeddableDistribution; FileName: string; + Header: TZipHeader; Position: Int64) of object; + + TPyCustomEmbeddableDistribution = class(TPyDistribution) + private + FEmbeddablePackage: string; + FEnvironmentPath: string; + FOnZipProgress: TZipProgress; + function FindSharedLibrary(): string; + function FindExecutable(): string; + private + procedure DoZipProgressEvt(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64); + {$IFDEF POSIX} + function FileIsExecutable(const AFilePath: string): boolean; + {$ENDIF POSIX} + procedure CreateSymlink(const ASymlink, ATarget: string); + protected + function EnvironmentExists(): boolean; + /// + /// Navigates through the embeddables searching for a compatible distribution. + /// + function EmbeddableExists(): boolean; + /// + /// Creates a new environment based on the current settings. + /// An embeddable distribution will be used as an "image". + /// + procedure CreateEnvironment(const ACancelation: ICancelation); virtual; + procedure CreateSymLinks(); virtual; + procedure LoadSettings(const ACancelation: ICancelation); virtual; + protected + function GetEnvironmentPath(): string; + public + function Setup(const ACancelation: ICancelation): boolean; override; + public + property EmbeddablePackage: string read FEmbeddablePackage write FEmbeddablePackage; + published + property EnvironmentPath: string read FEnvironmentPath write FEnvironmentPath; + property OnZipProgress: TZipProgress read FOnZipProgress write FOnZipProgress; + end; + + TPyEmbeddableDistribution = class(TPyCustomEmbeddableDistribution) + private + FScanned: boolean; + FDeleteEmbeddable: boolean; + procedure DoDeleteEmbeddable(); + protected + procedure LoadSettings(const ACancelation: ICancelation); override; + public + function Setup(const ACancelation: ICancelation): boolean; override; + property Scanned: boolean read FScanned write FScanned; + published + property EmbeddablePackage; + /// + /// Delete the embeddable zip file after install. + /// + property DeleteEmbeddable: boolean read FDeleteEmbeddable write FDeleteEmbeddable; + end; + + TPyEmbeddableCustomCollection = class(TPyDistributionCollection); + + TPyEmbeddableCollection = class(TPyEmbeddableCustomCollection); + + TPyCustomEmbeddedEnvironment = class(TPyEnvironment) + private + FOnZipProgress: TZipProgress; + published + property OnZipProgress: TZipProgress read FOnZipProgress write FOnZipProgress; + end; + + [ComponentPlatforms(pidAllPlatforms)] + TPyEmbeddedEnvironment = class(TPyCustomEmbeddedEnvironment) + public type + TScanRule = (srFolder, srFileName); + private type + TScanner = class(TPersistent) + private + FAutoScan: boolean; + FScanRule: TScanRule; + FEmbeddablesPath: string; + FEnvironmentPath: string; + FDeleteEmbeddable: boolean; + public + procedure Scan(const AEmbedabblesPath: string; ACallback: TProc); + published + property AutoScan: boolean read FAutoScan write FAutoScan default false; + property ScanRule: TScanRule read FScanRule write FScanRule; + property EmbeddablesPath: string read FEmbeddablesPath write FEmbeddablesPath; + /// + /// Default environment path. + /// + property EnvironmentPath: string read FEnvironmentPath write FEnvironmentPath; + /// + /// Delete the embeddable zip file after install. + /// + property DeleteEmbeddable: boolean read FDeleteEmbeddable write FDeleteEmbeddable; + end; + private + FScanner: TScanner; + procedure SetScanner(const Value: TScanner); + protected + function CreateCollection(): TPyDistributionCollection; override; + procedure Prepare(const ACancelation: ICancelation); override; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy(); override; + published + property Distributions; + property Scanner: TScanner read FScanner write SetScanner; + end; + +implementation + +uses + System.IOUtils, + System.Character, + System.StrUtils, + System.RegularExpressions, + PyTools.ExecCmd, + PyEnvironment.Exception, + PyEnvironment.Path + {$IFDEF POSIX} + , Posix.SysStat, Posix.Stdlib, Posix.String_, Posix.Errno, Posix.Unistd + {$ENDIF} + ; + +type + TZipEventToAnonMethodAdapter = class + public type + TAdapterProc = TProc; + private + FAdapterProc: TAdapterProc; + public + constructor Create(const AAdapterProc: TAdapterProc); + procedure Evt(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64); + end; + +{ TPyCustomEmbeddableDistribution } + +procedure TPyCustomEmbeddableDistribution.CreateEnvironment(const ACancelation: ICancelation); +var + LAdapter: TZipEventToAnonMethodAdapter; + LProgress: TZipEventToAnonMethodAdapter.TAdapterProc; +begin + LProgress := procedure(Sender: TObject; FileName: string; Header: TZipHeader; Position: Int64) + begin + ACancelation.CheckCancelled(); + DoZipProgressEvt(Sender, FileName, Header, Position); + end; + + LAdapter := TZipEventToAnonMethodAdapter.Create(LProgress); + try + TZipFile.ExtractZipFile(FEmbeddablePackage, GetEnvironmentPath(), LAdapter.Evt); + finally + LAdapter.Free(); + end; +end; + +procedure TPyCustomEmbeddableDistribution.CreateSymlink(const ASymlink, + ATarget: string); +var + LExistingTarget: string; +begin + LExistingTarget := String.Empty; + if TFile.Exists(ATarget) then begin + //There is a bug with TFile.Exists and TFile.GetSymLinkTarget for Android + //So we recreate it every time + DeleteFile(ASymlink); + + if not TDirectory.Exists(TPath.GetDirectoryName(ASymlink)) then + TDirectory.CreateDirectory(TPath.GetDirectoryName(ASymlink)); + + if not TFile.CreateSymLink(ASymlink, ATarget) then + raise ESymlinkFailed.CreateFmt('Failed to create the symlink: %s -> %s', [ASymlink, ATarget]); + end; +end; + +procedure TPyCustomEmbeddableDistribution.CreateSymLinks; +var + LTargetInterpreter: string; + LTargetLauncher: string; + LSymlinkInterpreter: string; + LSynlinkLauncher: string; +begin + {$IFNDEF ANDROID} + Exit; + {$ENDIF ANDROID} + //Real file names + LTargetInterpreter := TPath.Combine( + TPath.GetLibraryPath(), + String.Format('libpython%s.so', [PythonVersion])); + LTargetLauncher := TPath.Combine( + TPath.GetLibraryPath(), + String.Format('libpythonlauncher%s.so', [PythonVersion])); + //Symlink names + LSymlinkInterpreter := TPath.Combine( + TPath.Combine(GetEnvironmentPath(), 'lib'), + String.Format('libpython%s.so', [PythonVersion])); + LSynlinkLauncher := TPath.Combine( + TPath.Combine(GetEnvironmentPath(), 'bin'), + String.Format('python%s', [PythonVersion])); + //Creates the interpreter symlink + CreateSymlink(LSymlinkInterpreter, LTargetInterpreter); + //Creates the launcher symlink + CreateSymlink(LSynlinkLauncher, LTargetLauncher); +end; + +procedure TPyCustomEmbeddableDistribution.DoZipProgressEvt(Sender: TObject; FileName: string; + Header: TZipHeader; Position: Int64); +begin + if Assigned(FOnZipProgress) then + FOnZipProgress(Sender, Self, FileName, Header, Position); +end; + +function TPyCustomEmbeddableDistribution.EmbeddableExists: boolean; +begin + Result := TFile.Exists(FEmbeddablePackage); +end; + +function TPyCustomEmbeddableDistribution.EnvironmentExists: boolean; +begin + Result := TDirectory.Exists(GetEnvironmentPath()); +end; + +{$IFDEF POSIX} +function TPyCustomEmbeddableDistribution.FileIsExecutable( + const AFilePath: string): boolean; +begin + {$WARN SYMBOL_PLATFORM OFF} + //Avoiding symlinks + Result := (TFileAttribute.faOwnerExecute in TFile.GetAttributes(AFilePath)) + or (TFileAttribute.faGroupExecute in TFile.GetAttributes(AFilePath)) + or (TFileAttribute.faOthersExecute in TFile.GetAttributes(AFilePath)); + {$WARN SYMBOL_PLATFORM ON} +end; +{$ENDIF POSIX} + +function TPyCustomEmbeddableDistribution.FindExecutable: string; + + function DoSearch(const APath: string): TArray; + {$IFDEF POSIX} + var + LFile: string; + {$ENDIF POSIX} + begin + Result := TDirectory.GetFiles(APath, 'python*', TSearchOption.soTopDirectoryOnly, + function(const Path: string; const SearchRec: TSearchRec): boolean + var + LFileName: string; + begin + LFileName := SearchRec.Name; + if LFileName.EndsWith('m') then //3.7 and lower contain a "m" as sufix. + LFileName := LFileName.Remove(Length(LFileName) - 1); + + Result := Char.IsDigit(LFileName, Length(LFileName) - 1); + end); + + {$IFDEF POSIX} + for LFile in Result do begin + if (TPath.GetFileName(LFile).StartsWith('python' + PythonVersion)) and (FileIsExecutable(LFile)) then + Exit(TArray.Create(LFile)); + end; + {$ENDIF POSIX} + end; + +var + LFiles: TArray; +begin + {$IFDEF MSWINDOWS} + //If we get this far and we're in a Windows only section + //then we're done so just exit with the Result intact + Result := TPath.Combine(GetEnvironmentPath(), 'python.exe'); + if TFile.Exists(Result) then + Exit(Result) + else + Exit(String.Empty); + {$ELSEIF DEFINED(IOS64)} + Exit(String.Empty); + {$ELSE} + Result := TPath.Combine(GetEnvironmentPath(), 'bin'); + {$ENDIF} + + LFiles := DoSearch(Result); + if Length(LFiles) > 0 then begin + Result := LFiles[Low(LFiles)]; + if not TFile.Exists(Result) then + Result := String.Empty; + end else + Result := String.Empty; +end; + +function TPyCustomEmbeddableDistribution.FindSharedLibrary: string; + + function DoSearch(const ALibName: string; const APath: string; + const ASearchOption: TSearchOption = TSearchOption.soTopDirectoryOnly): TArray; + var + LFile: string; + LSearch: string; + begin + LFile := TPath.Combine(APath, ALibName); + if TFile.Exists(LFile) then + Exit(TArray.Create(LFile)); + + LSearch := ALibName.Replace(TPath.GetExtension(ALibName), '') + '*' + TPath.GetExtension(ALibName); + Result := TDirectory.GetFiles( + APath, + LSearch, //Python <= 3.7 might contain a "m" as sufix. + ASearchOption); + end; + +var + I: integer; + LLibName: string; + LPath: string; + LFiles: TArray; +begin + for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do + if PythonVersion.StartsWith(PYTHON_KNOWN_VERSIONS[I].RegVersion) then begin + LLibName := PYTHON_KNOWN_VERSIONS[I].DllName; + Break; + end; + + {$IFDEF MSWINDOWS} + LPath := GetEnvironmentPath(); + {$ELSEIF DEFINED(IOS)} + // The Python shared library is distributed as framework on iOS + LPath := TPyEnvironmentPath.ResolvePath(TPyEnvironmentPath.ENVIRONMENT_PATH); + LPath := TPath.Combine(LPath, 'Frameworks', 'libpython3.framework'); + {$ELSEIF DEFINED(MACOS)} + //Let's try it in the frameworks path first + LPath := TPath.Combine( + TDirectory.GetParent(TPath.GetDirectoryName(GetModuleName(HInstance))), + 'Frameworks'); + LFiles := DoSearch(LLibName, LPath, TSearchOption.soAllDirectories); + if LFiles <> nil then + Exit(LFiles[Low(LFiles)]); + + LPath := TPyEnvironmentPath.ResolvePath(TPyEnvironmentPath.ENVIRONMENT_PATH); + LFiles := DoSearch(LLibName, LPath); + if LFiles <> nil then + Exit(LFiles[Low(LFiles)]); + //Try to find it in the environment folder + LPath := TPath.Combine(GetEnvironmentPath(), 'lib'); + {$ELSE} + LPath := TPath.Combine(GetEnvironmentPath(), 'lib'); + {$ENDIF} + + LFiles := DoSearch(LLibName, LPath); + if LFiles <> nil then + Result := LFiles[Low(LFiles)] + else + Result := String.Empty; + + {$IFDEF LINUX} + //Targets directly to the so file instead of a symlink. + if TFile.Exists(Result + '.1.0') then + Result := Result + '.1.0'; + {$ENDIF} +end; + +procedure TPyCustomEmbeddableDistribution.LoadSettings(const ACancelation: ICancelation); +begin + ACancelation.CheckCancelled(); + + Home := GetEnvironmentPath(); + {$IFDEF IOS} + Path := GetEnvironmentPath(); + {$ENDIF IOS} + SharedLibrary := FindSharedLibrary(); + Executable := FindExecutable(); +end; + +function TPyCustomEmbeddableDistribution.GetEnvironmentPath: string; +begin + Result := TPyEnvironmentPath.ResolvePath(EnvironmentPath, PythonVersion); +end; + +function TPyCustomEmbeddableDistribution.Setup(const ACancelation: ICancelation): boolean; +begin + inherited; + if not EnvironmentExists() then begin + if not EmbeddableExists() then + raise EEmbeddableNotFound.CreateFmt( + 'Embeddable package not found.' + #13#10 + '%s', [FEmbeddablePackage]); + + CreateEnvironment(ACancelation); + end; + + CreateSymLinks(); + + LoadSettings(ACancelation); + + Result := true; +end; + +{ TPyEmbeddableDistribution } + +procedure TPyEmbeddableDistribution.DoDeleteEmbeddable; +begin + TFile.Delete(EmbeddablePackage); +end; + +procedure TPyEmbeddableDistribution.LoadSettings(const ACancelation: ICancelation); +begin + if FScanned then + inherited; +end; + +function TPyEmbeddableDistribution.Setup(const ACancelation: ICancelation): boolean; +begin + Result := inherited; + if FDeleteEmbeddable and EmbeddableExists() then + DoDeleteEmbeddable(); +end; + +{ TPyEmbeddedEnvironment } + +constructor TPyEmbeddedEnvironment.Create(AOwner: TComponent); +begin + inherited; + FScanner := TScanner.Create(); + FScanner.ScanRule := TScanRule.srFolder; + if not (csDesigning in ComponentState) then begin + FScanner.EnvironmentPath := TPyEnvironmentPath.CreateEnvironmentPath(); + FScanner.EmbeddablesPath := TPyEnvironmentPath.CreateEmbeddablesPath(); + end; +end; + +destructor TPyEmbeddedEnvironment.Destroy; +begin + FScanner.Free(); + inherited; +end; + +function TPyEmbeddedEnvironment.CreateCollection: TPyDistributionCollection; +begin + Result := TPyEmbeddableCollection.Create(Self, TPyEmbeddableDistribution); +end; + +procedure TPyEmbeddedEnvironment.Prepare(const ACancelation: ICancelation); +var + I: integer; + LExistingEnvironment: string; + LDistribution: TPyEmbeddableDistribution; +begin + if FScanner.AutoScan then begin + //Let's first look for existing environments + for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin + LExistingEnvironment := TPyEnvironmentPath.ResolvePath( + FScanner.EnvironmentPath, + PYTHON_KNOWN_VERSIONS[I].RegVersion); + + if TDirectory.Exists(LExistingEnvironment) then begin + LDistribution := TPyEmbeddableDistribution(Distributions.Add()); + LDistribution.Scanned := true; + LDistribution.PythonVersion := PYTHON_KNOWN_VERSIONS[I].RegVersion; + LDistribution.EnvironmentPath := LExistingEnvironment; + end; + end; + + FScanner.Scan( + TPyEnvironmentPath.ResolvePath(FScanner.EmbeddablesPath), + procedure(APyVersionInfo: TPythonVersionProp; AEmbeddablePackage: string) begin + if Assigned(Distributions.LocateEnvironment(APyVersionInfo.RegVersion)) then + Exit; + + LDistribution := TPyEmbeddableDistribution(Distributions.Add()); + LDistribution.Scanned := true; + LDistribution.PythonVersion := APyVersionInfo.RegVersion; + LDistribution.EnvironmentPath := TPyEnvironmentPath.ResolvePath( + FScanner.EnvironmentPath, + APyVersionInfo.RegVersion); + LDistribution.EmbeddablePackage := AEmbeddablePackage; + LDistribution.OnZipProgress := FOnZipProgress; + LDistribution.DeleteEmbeddable := FScanner.DeleteEmbeddable; + end); + end; + inherited; +end; + +procedure TPyEmbeddedEnvironment.SetScanner(const Value: TScanner); +begin + FScanner.Assign(Value); +end; + +{ TPyEmbeddedEnvironment.TScanner } + +procedure TPyEmbeddedEnvironment.TScanner.Scan(const AEmbedabblesPath: string; + ACallback: TProc); +var + I: Integer; + LPath: string; + LFiles: TArray; + LPythonVersion: string; + LSearchPatter: string; +begin + if not Assigned(ACallback) then + Exit; + + if not TDirectory.Exists(AEmbedabblesPath) then + raise EDirectoryNotFoundException.CreateFmt('Directory "%s" not found.', [ + AEmbedabblesPath]); + + //Look for version named subfolders + if (FScanRule = TScanRule.srFolder) then begin + LSearchPatter := '*.zip'; + for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin + LPath := TPath.Combine(AEmbedabblesPath, PYTHON_KNOWN_VERSIONS[I].RegVersion); + if not TDirectory.Exists(LPath) then + Continue; + + LFiles := TDirectory.GetFiles(LPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); + if (Length(LFiles) = 0) then + Continue; + + ACallback(PYTHON_KNOWN_VERSIONS[I], LFiles[0]); + end; + end else if (FScanRule = TScanRule.srFileName) then begin + //Look for pattern named files + for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin + LPythonVersion := PYTHON_KNOWN_VERSIONS[I].RegVersion; + LSearchPatter := Format('*python3-*-%s*.zip', [LPythonVersion]); + LFiles := TDirectory.GetFiles(AEmbedabblesPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); + if (Length(LFiles) = 0) then + Continue; + + ACallback(PYTHON_KNOWN_VERSIONS[I], LFiles[0]); + end + end; +end; + +{ TZipEventToAnonMethodAdapter } + +constructor TZipEventToAnonMethodAdapter.Create(const AAdapterProc: TAdapterProc); +begin + FAdapterProc := AAdapterProc; +end; + +procedure TZipEventToAnonMethodAdapter.Evt(Sender: TObject; FileName: string; + Header: TZipHeader; Position: Int64); +begin + FAdapterProc(Sender, FileName, Header, Position); +end; + +end. diff --git a/src/Embeddable/Res/python37.inc b/src/Embeddable/Res/python37.inc deleted file mode 100644 index ee2bf5e..0000000 --- a/src/Embeddable/Res/python37.inc +++ /dev/null @@ -1,37 +0,0 @@ -{$IFDEF MSWINDOWS} - {$IFDEF WIN32} - {$R ..\..\..\resources\python3-windows-3.7-win32.res} - {$ENDIF WIN32} - {$IFDEF WIN64} - {$R ..\..\..\resources\python3-windows-3.7-amd64.res} - {$ENDIF WIN64} -{$ENDIF MSWINDOWS} - -{$IFDEF LINUX} - {$IFDEF LINUX64} - {$R ..\..\..\resources\python3-linux-3.7-x86_64.res} - {$ENDIF LINUX64} -{$ENDIF LINUX} - -{$IFDEF MACOS} - {$IFDEF MACOS64} - {$IFDEF CPUARM} - //{$R ..\..\..\resources\python3-macos-3.7-universal2.res} - {$ELSE} - {$R ..\..\..\resources\python3-macos-3.7-x86_64.res} - {$ENDIF CPUARM} - {$ENDIF MACOS64} -{$ENDIF MACOS} - -{$IFDEF ANDROID} - {$IFDEF CPUARM32} - {$IFDEF ANDROID32} - {$R ..\..\..\resources\python3-android-3.7-arm.res} - {$ENDIF ANDROID32} - {$ENDIF CPUARM32} - {$IFDEF CPUARM64} - {$IFDEF ANDROID64} - {$R ..\..\..\resources\python3-android-3.7-arm64.res} - {$ENDIF ANDROID64} - {$ENDIF CPUARM64} -{$ENDIF ANDROID} diff --git a/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.Android.pas b/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.Android.pas new file mode 100644 index 0000000..44e209c --- /dev/null +++ b/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.Android.pas @@ -0,0 +1,281 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.Android' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for Android *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.Android; + +interface + +uses + System.SysUtils, + System.Classes, + ToolsAPI, + DeploymentAPI, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.Platform; + +type + TPyEnvironmentProjectDeployAndroid = class(TPyEnvironmentProjectDeployPlatform) + private + function GetAssetsFolder(const APythonVersion: string): string; inline; + function GetExistingAssets: TArray; + private + /// + /// Opens the Python distribution .zip collecting the .so files + /// to make assets. + /// + function MakeAssetsFromZip: TArray; + /// + /// Delete all assets. + /// + procedure ClearAssets; + /// + /// The minimal Python bundle to be distributed to Android. + /// Excludes many unnecessary files. + /// + function MakePythonMinimalBundle: string; + protected + function GetBundleMinimalIgnoresList: TArray; override; + function Build: TArray; override; + end; + + +implementation + +uses + System.IOUtils, + System.Masks, + System.Zip, + PyEnvironment.Project.IDE.Deploy; + +{ TPyEnvironmentProjectDeployAndroid } + +function TPyEnvironmentProjectDeployAndroid.GetAssetsFolder( + const APythonVersion: string): string; +begin + // Deploy assets to the project's build folder + var LConfigFolder := (BorlandIDEServices as IOTAServices) + .ExpandRootMacro('$(Config)'); + + Result := TPath.Combine( + GetProjectFolder(), + GetPlatform().ToString(), + LConfigFolder, + TPath.GetFileNameWithoutExtension(ProjectFileName) + + '.' + + 'python' + APythonVersion + + '.' + + 'Assets'); +end; + +function TPyEnvironmentProjectDeployAndroid.GetBundleMinimalIgnoresList: TArray; +begin + Result := inherited + [ + 'bin/*', + // Remove the Python shared libs + 'lib/libpython*.so*' + ]; +end; + +function TPyEnvironmentProjectDeployAndroid.GetExistingAssets: TArray; +begin + Result := TDirectory.GetFiles(GetAssetsFolder(PythonVersion), + TSearchOption.soAllDirectories, + function(const AFileName: string; const SearchRec: TSearchRec): boolean + begin + Result := String(SearchRec.Name).EndsWith('.so'); + end); +end; + +procedure TPyEnvironmentProjectDeployAndroid.ClearAssets; +begin + for var LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do begin + var LAssetsFolder := GetAssetsFolder(LPythonVersion); + if TDirectory.Exists(LAssetsFolder) then + TDirectory.Delete(LAssetsFolder, true); + end; +end; + +function TPyEnvironmentProjectDeployAndroid.MakeAssetsFromZip: TArray; +var + LStream: TStream; + LLocalHeader: TZipHeader; +begin + if TDirectory.Exists(GetAssetsFolder(PythonVersion)) then + Exit(GetExistingAssets()); + + // It is the first time set or something has changed. Let's clean up. + ClearAssets(); + + // Make the assets folder + TDirectory.CreateDirectory(GetAssetsFolder(PythonVersion)); + + var LZip := TZipFile.Create; + try + LZip.Open(LocatePythonBundle(), TZipMode.zmRead); + + // Is the libpythonxx.so.1.0 the real one? + if LZip.IndexOf(Format('lib/libpython%s.so.1.0', [PythonVersion])) >= 0 then begin + var LAssetFileName := TPath.Combine( + GetAssetsFolder(PythonVersion), + Format('libpython%s.so', [PythonVersion])); + + if TFile.Exists(LAssetFileName) then + TFile.Delete(LAssetFileName); + + var LAssetStream := TFileStream.Create(LAssetFileName, fmCreate); + try + LZip.Read(Format('lib/libpython%s.so.1.0', [PythonVersion]), LStream, LLocalHeader); + LAssetStream.CopyFrom(LStream, LStream.Size); + finally + LAssetStream.Free; + end; + + Result := Result + [LAssetFileName]; + end; + + // We need to name the python launcher with the "lib" prefix + var LAssetFileName := TPath.Combine( + GetAssetsFolder(PythonVersion), + Format('libpythonlauncher%s.so', [PythonVersion])); + var LAssetStream := TFileStream.Create(LAssetFileName, fmCreate); + try + LZip.Read('bin/python' + PythonVersion, LStream, LLocalHeader); + LAssetStream.CopyFrom(LStream, LStream.Size); + finally + LAssetStream.Free; + end; + + Result := Result + [LAssetFileName]; + + LZip.Close; + finally + LZip.Free; + end; +end; + +function TPyEnvironmentProjectDeployAndroid.MakePythonMinimalBundle: string; + + function ShouldIgnore(const AFileName: string): boolean; + begin + for var LIgnore in GetBundleMinimalIgnoresList() do + if MatchesMask(AFileName, LIgnore) then + Exit(true); + + Result := false; + end; + + procedure ReadImage(const APythonBundle: string; const ACallback: TProc); + var + LStream: TStream; + LLocalHeader: TZipHeader; + begin + Assert(Assigned(ACallback), 'Parameter "ACallback" not assigned.'); + + var LZipReader := TZipFile.Create; + try + LZipReader.Open(APythonBundle, TZipMode.zmRead); + for var LFileName in LZipReader.FileNames do + begin + if ShouldIgnore(LFileName) then + Continue; + + LZipReader.Read(LFileName, LStream, LLocalHeader); + ACallback(LStream, LLocalHeader, LFileName); + end; + LZipReader.Close; + finally + LZipReader.Free; + end; + end; + +begin + // Create a minimal Python bundle + Result := GetBundleMinimalFileName(); + + // Always rebuild ??? Not now... + if TFile.Exists(Result) then + Exit(Result); + + if not TDirectory.Exists(TPath.GetDirectoryName(Result)) then + TDirectory.CreateDirectory(TPath.GetDirectoryName(Result)); + + var LZipWriter := TZipFile.Create(); + try + LZipWriter.Open(Result, TZipMode.zmWrite); + // Iterate over the image bundle and create the minimal bundle + ReadImage(LocatePythonBundle(), + procedure(AData: TStream; AZipHeader: TZipHeader; AFileName: TFileName) begin + if not String(AFileName).IsEmpty then + LZipWriter.Add( + AData, + AFileName); + end); + LZipWriter.Close; + finally + LZipWriter.Free; + end; +end; + +function TPyEnvironmentProjectDeployAndroid.Build: TArray; +begin + // Add assets to the deploy file list + for var LAsset: string in MakeAssetsFromZip() do + begin + var LDeployOp := TDeployOperation.doCopyOnly; + if LAsset.EndsWith('.so') then + LDeployOp := TDeployOperation.doSetExecBit; + + var LDestFolder := 'library\lib\armeabi-v7a\'; + if GetPlatform() = TPyEnvironmentProjectPlatform.Android64 then + LDestFolder := 'library\lib\arm64-v8a\'; + + Result := Result + [ + TPyEnvironmentDeployFile.Create(GetPlatform(), + // Make the asset path relative to project's path + LAsset.Replace(IncludeTrailingPathDelimiter(GetProjectFolder()), '', []), + LDestFolder, + true, true, LDeployOp, '', false) + ]; + end; + + // Add the python minimal bundle to the deploy file list + var LPythonLibZip := MakePythonMinimalBundle(); + Result := Result + [ + TPyEnvironmentDeployFile.Create(GetPlatform(), + LPythonLibZip.Replace(IncludeTrailingPathDelimiter(PythonEnvironmentFolder), '', []), + // Extracts to ./PYVER by default + '.\assets\internal', + false, true, + TDeployOperation.doCopyOnly, //It seems Android doesn't support doUnArchive :( + '') + ]; +end; + +end. diff --git a/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.AndroidARM.pas b/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.AndroidARM.pas new file mode 100644 index 0000000..3c00854 --- /dev/null +++ b/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.AndroidARM.pas @@ -0,0 +1,73 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.AndroidARM' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for Android ARM *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.AndroidARM; + +interface + +uses + System.SysUtils, + System.Classes, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.Android; + +type + TPyEnvironmentProjectDeployAndroidARM = class(TPyEnvironmentProjectDeployAndroid) + protected + function GetPlatform: TPyEnvironmentProjectPlatform; override; + function GetPythonBundleName: string; override; + end; + +implementation + +uses + System.StrUtils; + +{ TPyEnvironmentProjectDeployAndroidARM } + +function TPyEnvironmentProjectDeployAndroidARM.GetPlatform: TPyEnvironmentProjectPlatform; +begin + Result := TPyEnvironmentProjectPlatform.Android; +end; + +function TPyEnvironmentProjectDeployAndroidARM.GetPythonBundleName: string; +begin + case IndexStr(PythonVersion, ['3.8', '3.9', '3.10', '3.11', '3.12']) of + 0: Result := 'python3-android-3.8.16-arm.zip'; + 1: Result := 'python3-android-3.9.16-arm.zip'; + 2: Result := 'python3-android-3.10.7-arm.zip'; + 3: Result := 'python3-android-3.11.2-arm.zip'; + 4: Result := 'python3-android-3.12.0-arm.zip'; + else + Result := String.Empty; + end; +end; + +end. diff --git a/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.AndroidARM64.pas b/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.AndroidARM64.pas new file mode 100644 index 0000000..d7ebd43 --- /dev/null +++ b/src/Project/IDE/Deploy/Android/PyEnvironment.Project.IDE.Deploy.AndroidARM64.pas @@ -0,0 +1,87 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.AndroidARM64' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for Android ARM64 *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.AndroidARM64; + +interface + +uses + System.SysUtils, + System.Classes, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.Android; + +type + TPyEnvironmentProjectDeployAndroidARM64 = class(TPyEnvironmentProjectDeployAndroid) + protected + function GetPlatform: TPyEnvironmentProjectPlatform; override; + function GetPythonBundleName: string; override; + function Build: TArray; override; + end; + +implementation + +uses + System.StrUtils, + PyEnvironment.Project.IDE.Deploy.AndroidARM; + +{ TPyEnvironmentProjectDeployAndroidARM64 } + +function TPyEnvironmentProjectDeployAndroidARM64.Build: TArray; +begin + Result := inherited; + + var LARMDeployables := TPyEnvironmentProjectDeployAndroidARM + .GetDeployables(ProjectFileName, PythonEnvironmentFolder, PythonVersion); + for var I := Low(LARMDeployables) to High(LARMDeployables) do + LARMDeployables[I].Condition := '''$(AndroidAppBundle)''==''true'''; + + Result := Result + LARMDeployables; +end; + +function TPyEnvironmentProjectDeployAndroidARM64.GetPlatform: TPyEnvironmentProjectPlatform; +begin + Result := TPyEnvironmentProjectPlatform.Android64; +end; + +function TPyEnvironmentProjectDeployAndroidARM64.GetPythonBundleName: string; +begin + case IndexStr(PythonVersion, ['3.8', '3.9', '3.10', '3.11', '3.12']) of + 0: Result := 'python3-android-3.8.16-arm64.zip'; + 1: Result := 'python3-android-3.9.16-arm64.zip'; + 2: Result := 'python3-android-3.10.7-arm64.zip'; + 3: Result := 'python3-android-3.11.2-arm64.zip'; + 4: Result := 'python3-android-3.12.0-arm64.zip'; + else + Result := String.Empty; + end; +end; + +end. diff --git a/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSX.pas b/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSX.pas new file mode 100644 index 0000000..e329754 --- /dev/null +++ b/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSX.pas @@ -0,0 +1,356 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.macOS' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for macOS *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.OSX; + +interface + +uses + System.SysUtils, + System.Classes, + ToolsAPI, + DeploymentAPI, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.Platform; + +type + // https://developer.apple.com/documentation/bundleresources/placing_content_in_a_bundle + // Apple docs says that we need to place dynlibs onto Contents/Frameworks + // It seems to work fine as is, but this is something to think about... + + TPyEnvironmentProjectDeployOSX = class(TPyEnvironmentProjectDeployPlatform) + private + function GetFrameworksFolder(const APythonVersion: string): string; inline; + function GetExistingFrameworks: TArray; + private + /// + /// Create the frameworks template plist for the current project. + /// + function MakeFrameworkTemplatePlist: string; + /// + /// Create a framework for a single .dylib including a Info.plist for code sign. + /// + function MakeFramework(const ALibraryName: string; + const ACopyFileProc: TProc): TArray; + /// + /// Opens the Python distribution .zip collecting the .dylib files + /// to make frameworks. + /// + function MakeFrameworksFromZip: TArray; + /// + /// Delete all frameworks folders. + /// + procedure ClearFrameworks; + /// + /// The minimal Python bundle to be distributed to OSX. + /// Excludes many unnecessary files. + /// + function MakePythonMinimalBundle: string; + protected + function GetPythonBundleName: string; override; + function GetBundleMinimalIgnoresList: TArray; override; + function Build: TArray; override; + end; + +const + INFO_PLIST_TEMPLATE_NAME = 'info.plist.framework.TemplateOSX.xml'; + + FRAMEWORK_TEMPLATE_PLIST = + '' + sLineBreak + + '' + sLineBreak + + '' + sLineBreak + + '' + sLineBreak + + ' CFBundleDevelopmentRegion' + sLineBreak + + ' English' + sLineBreak + + ' CFBundleExecutable' + sLineBreak + + ' %CFBundleExecutable%' + sLineBreak + + ' CFBundleIdentifier' + sLineBreak + + ' %CFBundleIdentifier%' + sLineBreak + + ' CFBundleInfoDictionaryVersion' + sLineBreak + + ' 6.0' + sLineBreak + + ' CFBundlePackageType' + sLineBreak + + ' FMWK' + sLineBreak + + ' CFBundleSignature' + sLineBreak + + ' ????' + sLineBreak + + ' CFBundleVersion' + sLineBreak + + ' 1' + sLineBreak + + ' CFBundleShortVersionString' + sLineBreak + + ' 1' + sLineBreak + + ' CSResourcesFileMapped' + sLineBreak + + ' ' + sLineBreak + + '' + sLineBreak + + ''; + +implementation + +uses + System.StrUtils, + System.IOUtils, + System.Masks, + System.Zip, + PyEnvironment.Project.IDE.Deploy; + +{ TPyEnvironmentProjectDeployOSX } + +procedure TPyEnvironmentProjectDeployOSX.ClearFrameworks; +begin + for var LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do begin + var LFrameworksFolder := GetFrameworksFolder(LPythonVersion); + if TDirectory.Exists(LFrameworksFolder) then + TDirectory.Delete(LFrameworksFolder, true); + end; +end; + +function TPyEnvironmentProjectDeployOSX.GetBundleMinimalIgnoresList: TArray; +begin + Result := inherited + [ + // Remove the Python shared lib - this MUST be a framework + 'lib/*.dylib' + ]; +end; + +function TPyEnvironmentProjectDeployOSX.GetExistingFrameworks: TArray; +begin + Result := TDirectory.GetFiles(GetFrameworksFolder(PythonVersion), + TSearchOption.soAllDirectories, + function(const AFileName: string; const SearchRec: TSearchRec): boolean + begin + Result := String(SearchRec.Name).EndsWith('.dylib') or String(SearchRec.Name).EndsWith('.plist'); + end); +end; + +function TPyEnvironmentProjectDeployOSX.GetFrameworksFolder( + const APythonVersion: string): string; +begin + // Deploy frameworks to the project's build folder + var LConfigFolder := (BorlandIDEServices as IOTAServices) + .ExpandRootMacro('$(Config)'); + + Result := TPath.Combine( + GetProjectFolder(), + GetPlatform().ToString(), + LConfigFolder, + TPath.GetFileNameWithoutExtension(ProjectFileName) + + '.' + + 'python' + APythonVersion + + '.' + + 'Frameworks'); +end; + +function TPyEnvironmentProjectDeployOSX.GetPythonBundleName: string; +begin + case IndexStr(PythonVersion, ['3.8', '3.9', '3.10', '3.11', '3.12']) of + 0: Result := 'python3-macos-3.8.18-universal2.zip'; + 1: Result := 'python3-macos-3.9.18-universal2.zip'; + 2: Result := 'python3-macos-3.10.13-universal2.zip'; + 3: Result := 'python3-macos-3.11.6-universal2.zip'; + 4: Result := 'python3-macos-3.12.0-universal2.zip'; + else + Result := String.Empty; + end; +end; + +function TPyEnvironmentProjectDeployOSX.MakeFramework( + const ALibraryName: string; + const ACopyFileProc: TProc): TArray; +begin + // The bundle indentifier is base upon its base name. + var LBundleIdentifier := IfThen(ALibraryName.Contains('.'), ALibraryName.Split(['.'])[0], ALibraryName); + // The framework folder name. + var LFrameworkName := LBundleIdentifier + '.framework'; + // The framework folder located in the project root folder. + var LFrameworkFolder := TPath.Combine(GetFrameworksFolder(PythonVersion), LFrameworkName); + // The framework library name; this is the distributed .dylib file located + // in the framework folder. + var LLibraryName := TPath.Combine(LFrameworkFolder, ALibraryName); + // Frameworks requires plist. This is the template plist. + var LInfoPlistTemplateFileName := MakeFrameworkTemplatePlist(); + // The framework plist. It goes alongside .dylib + var LInfoPlistFileName := TPath.Combine(LFrameworkFolder, 'Info.plist'); + + // Create the framework directory + TDirectory.CreateDirectory(LFrameworkFolder); + + // Copy the framework dynamic library to the framework folder + var LStream := TFileStream.Create(LLibraryName, fmCreate); + try + ACopyFileProc(LStream); + finally + LStream.Free; + end; + + // Loads the Info.plist template + { TODO : Must rebuild info.plist with project's info } + var LBuff := TFile.ReadAllText(LInfoPlistTemplateFileName); // raise exception if template doesn't exist + LBuff := LBuff + .Replace('%CFBundleExecutable%', ALibraryName, []) + .Replace('%CFBundleIdentifier%', Format('%s.%s', [ + TPath.GetFileNameWithoutExtension(GetProjectName()), + LBundleIdentifier]), []); + // Creates the framework Info.plist file + TFile.WriteAllText(LInfoPlistFileName, LBuff, TEncoding.UTF8); + + // This is the framework files + Result := [LLibraryName, LInfoPlistFileName]; +end; + +function TPyEnvironmentProjectDeployOSX.MakeFrameworksFromZip: TArray; +begin + if TDirectory.Exists(GetFrameworksFolder(PythonVersion)) then + Exit(GetExistingFrameworks()); + + // It is the first time set or something has changed. Let's clean up. + ClearFrameworks(); + + var LZip := TZipFile.Create; + try + LZip.Open(LocatePythonBundle(), TZipMode.zmRead); + + var LAssetFileName := Format('libpython%s.dylib', [PythonVersion]); + + Result := Result + MakeFramework(TPath.GetFileName(LAssetFileName), + procedure(AStream: TStream) + var + LStream: TStream; + LLocalHeader: TZipHeader; + begin + LZip.Read('lib/' + LAssetFileName, LStream, LLocalHeader); + AStream.CopyFrom(LStream, LStream.Size); + end); + + LZip.Close; + finally + LZip.Free; + end; +end; + +function TPyEnvironmentProjectDeployOSX.MakeFrameworkTemplatePlist: string; +begin + Result := TPath.Combine(GetProjectFolder(), INFO_PLIST_TEMPLATE_NAME); + if not TFile.Exists(Result) then + TFile.WriteAllText(Result, FRAMEWORK_TEMPLATE_PLIST, TEncoding.UTF8); +end; + +function TPyEnvironmentProjectDeployOSX.MakePythonMinimalBundle: string; + + function ShouldIgnore(const AFileName: string): boolean; + begin + for var LIgnore in GetBundleMinimalIgnoresList() do + if MatchesMask(AFileName, LIgnore) then + Exit(true); + + Result := false; + end; + + procedure ReadImage(const APythonBundle: string; const ACallback: TProc); + var + LStream: TStream; + LLocalHeader: TZipHeader; + begin + Assert(Assigned(ACallback), 'Parameter "ACallback" not assigned.'); + + var LZipReader := TZipFile.Create; + try + LZipReader.Open(APythonBundle, TZipMode.zmRead); + for var LFileName in LZipReader.FileNames do + begin + if ShouldIgnore(LFileName) then + Continue; + + LZipReader.Read(LFileName, LStream, LLocalHeader); + ACallback(LStream, LLocalHeader, LFileName); + end; + LZipReader.Close; + finally + LZipReader.Free; + end; + end; + +begin + // Create a minimal Python bundle + Result := GetBundleMinimalFileName(); + + // Always rebuild ??? Not now... + if TFile.Exists(Result) then + Exit(Result); + + if not TDirectory.Exists(TPath.GetDirectoryName(Result)) then + TDirectory.CreateDirectory(TPath.GetDirectoryName(Result)); + + var LZipWriter := TZipFile.Create(); + try + LZipWriter.Open(Result, TZipMode.zmWrite); + // Iterate over the image bundle and create the minimal bundle + ReadImage(LocatePythonBundle(), + procedure(AData: TStream; AZipHeader: TZipHeader; AFileName: TFileName) begin + if not String(AFileName).IsEmpty then + LZipWriter.Add( + AData, + AFileName); + end); + LZipWriter.Close; + finally + LZipWriter.Free; + end; +end; + +function TPyEnvironmentProjectDeployOSX.Build: TArray; +begin + // Add frameworks to the deploy file list + for var LFramework in MakeFrameworksFromZip() do + begin + var LDeployOp := TDeployOperation.doCopyOnly; + var LDirs := LFramework.Split(TPath.DirectorySeparatorChar); + var LFrameworkName := LDirs[High(LDirs) - 1]; + + if LFramework.EndsWith('.dylib') then + LDeployOp := TDeployOperation.doSetExecBit; + + Result := Result + [ + TPyEnvironmentDeployFile.Create(GetPlatform(), + // Make the framework path relative to project's path + LFramework.Replace(IncludeTrailingPathDelimiter(GetProjectFolder), '', []), + 'Contents\Frameworks\' + LFrameworkName, + true, true, LDeployOp, '', false) + ]; + end; + + // Add the python minimal bundle to the deploy file list + var LPythonLibZip := MakePythonMinimalBundle(); + Result := Result + [ + TPyEnvironmentDeployFile.Create(GetPlatform(), + LPythonLibZip.Replace(IncludeTrailingPathDelimiter(PythonEnvironmentFolder), '', []), + // Extracts to ./PYVER by default + 'Contents\Resources\' + PythonVersion, + false, true, TDeployOperation.doUnArchive, '') + ]; +end; + +end. diff --git a/src/Embeddable/Res/PyEnvironment.Embeddable.Res.Python37.pas b/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSX64.pas similarity index 78% rename from src/Embeddable/Res/PyEnvironment.Embeddable.Res.Python37.pas rename to src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSX64.pas index b22c62e..8da4a04 100644 --- a/src/Embeddable/Res/PyEnvironment.Embeddable.Res.Python37.pas +++ b/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSX64.pas @@ -1,6 +1,6 @@ (**************************************************************************) (* *) -(* Module: Unit 'PyEnvironment.Embeddable.Python37' *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.macOS' *) (* *) (* Copyright (c) 2021 *) (* Lucas Moura Belo - lmbelo *) @@ -9,7 +9,7 @@ (* *) (* Project page: https://github.com/Embarcadero/PythonEnviroments *) (**************************************************************************) -(* Functionality: PyEnvironment Embeddable as a Resource *) +(* Functionality: Make deployables for macOS64 *) (* *) (* *) (**************************************************************************) @@ -28,30 +28,27 @@ (* confidential or legal reasons, everyone is free to derive a component *) (* or to generate a diff file to my or other original sources. *) (**************************************************************************) -unit PyEnvironment.Embeddable.Res.Python37; +unit PyEnvironment.Project.IDE.Deploy.OSX64; interface uses - System.Classes, - PyEnvironment.Embeddable.Res; + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.OSX; type - [ComponentPlatforms(pidAllPlatforms)] - TPyEmbeddedResEnvironment37 = class(TPyCustomEmbeddedResEnvironment) + TPyEnvironmentProjectDeployOSX64 = class(TPyEnvironmentProjectDeployOSX) protected - procedure SetPythonVersion(const Value: string); override; + function GetPlatform: TPyEnvironmentProjectPlatform; override; end; -{$I python37.inc} - implementation -{ TPyEmbeddedResEnvironment37 } +{ TPyEnvironmentProjectDeployOSX64 } -procedure TPyEmbeddedResEnvironment37.SetPythonVersion(const Value: string); +function TPyEnvironmentProjectDeployOSX64.GetPlatform: TPyEnvironmentProjectPlatform; begin - inherited SetPythonVersion('3.7'); + Result := TPyEnvironmentProjectPlatform.OSX64; end; end. diff --git a/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSXARM64.pas b/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSXARM64.pas new file mode 100644 index 0000000..ade7897 --- /dev/null +++ b/src/Project/IDE/Deploy/OSX/PyEnvironment.Project.IDE.Deploy.OSXARM64.pas @@ -0,0 +1,54 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.macOS' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for macOSARM64 *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.OSXARM64; + +interface + +uses + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.OSX; + +type + TPyEnvironmentProjectDeployOSXARM64 = class(TPyEnvironmentProjectDeployOSX) + protected + function GetPlatform: TPyEnvironmentProjectPlatform; override; + end; + +implementation + +{ TPyEnvironmentProjectDeployOSXARM64 } + +function TPyEnvironmentProjectDeployOSXARM64.GetPlatform: TPyEnvironmentProjectPlatform; +begin + Result := TPyEnvironmentProjectPlatform.OSXARM64; +end; + +end. diff --git a/src/Project/IDE/Deploy/PyEnvironment.Project.IDE.Deploy.Platform.pas b/src/Project/IDE/Deploy/PyEnvironment.Project.IDE.Deploy.Platform.pas new file mode 100644 index 0000000..77f070b --- /dev/null +++ b/src/Project/IDE/Deploy/PyEnvironment.Project.IDE.Deploy.Platform.pas @@ -0,0 +1,174 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.Platform' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for custom platforms *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.Platform; + +interface + +uses + System.SysUtils, + System.Classes, + ToolsAPI, + DeploymentAPI, + PyEnvironment.Project.IDE.Types; + +type + TPyEnvironmentProjectDeployPlatformClass = class of TPyEnvironmentProjectDeployPlatform; + + TPyEnvironmentProjectDeployPlatform = class + private + FProjectFileName: string; + FPythonEnvironmentFolder: string; + FPythonVersion: string; + protected + function GetProjectFolder: string; inline; + function GetProjectName: string; inline; + function GetBundleImageFolder: string; inline; + function GetBundleMinimalFileName: string; inline; + + function GetPlatform: TPyEnvironmentProjectPlatform; virtual; abstract; + function GetPythonBundleName: string; virtual; abstract; + function GetBundleMinimalIgnoresList: TArray; virtual; + + function Build: TArray; virtual; abstract; + protected + /// + /// Locate the Python bundle by version. + /// + function LocatePythonBundle: string; + public + constructor Create(const AProjectFileName, APythonEnvironmentFolder, + APythonVersion: string); + + class function GetDeployables(const AProjectFileName, APythonEnvironmentDir, + APythonVersion: string): TArray; + + property PythonVersion: string read FPythonVersion; + property ProjectFileName: string read FProjectFileName; + property PythonEnvironmentFolder: string read FPythonEnvironmentFolder; + end; + + +implementation + +uses + System.IOUtils; + +{ TPyEnvironmentProjectDeployPlatform } + +constructor TPyEnvironmentProjectDeployPlatform.Create(const AProjectFileName, + APythonEnvironmentFolder, APythonVersion: string); +begin + inherited Create(); + FProjectFileName := AProjectFileName; + FPythonEnvironmentFolder := APythonEnvironmentFolder; + FPythonVersion := APythonVersion; +end; + +function TPyEnvironmentProjectDeployPlatform.GetProjectFolder: string; +begin + Result := TPath.GetDirectoryName(FProjectFileName); +end; + +function TPyEnvironmentProjectDeployPlatform.GetProjectName: string; +begin + Result := TPath.GetFileName(FProjectFileName); +end; + +function TPyEnvironmentProjectDeployPlatform.GetBundleImageFolder: string; +begin + Result := TPath.Combine(PythonEnvironmentFolder, 'python'); +end; + +function TPyEnvironmentProjectDeployPlatform.GetBundleMinimalFileName: string; +begin + Result := TPath.Combine(PythonEnvironmentFolder, 'python', + 'min-' + TPath.GetFileName(LocatePythonBundle())); +end; + +function TPyEnvironmentProjectDeployPlatform.GetBundleMinimalIgnoresList: TArray; +begin + Result := [ + // Remove 2to3 + 'bin/2to3*', + // Remove IDLE + 'bin/idle*', + // Remove pydoc + 'bin/pydoc*', + // Remove pythonxx-config + 'bin/python*-config', + // We don't need C headers. + 'include/*', + // No man use. + 'share/*', + // Remove standard library test suites. + 'lib/python3.*/ctypes/test/*', 'lib/python3.*/distutils/tests/*', 'lib/python3.*/lib2to3/tests/*', 'lib/python3.*/sqlite3/test/*', 'lib/python3.*/test/*', + // Remove config-* directory, which is used for compiling C extension modules. + 'lib/python3.*/config-*', + // Remove pydoc + 'lib/pydoc_data', + // Remove ensurepip. If user code needs pip, it can add it to + 'lib/python3.*/ensurepip/*', + // Remove libraries supporting IDLE. We don't need to ship an IDE + 'lib/python3.*/idlelib/*', + // Remove Tcl/Tk GUI code. + 'lib/python3.*/tkinter/*', 'lib/python3.*/turtle.py', 'lib/python3.*/turtledemo/*', + // Remove sysconfigdata + 'lib/python3.*/_sysconfigdata/*', + // Remove command-line curses toolkit. + 'lib/python3.*/curses/*', + // Remove pyc files. These take up space, but since most stdlib modules are never imported by user code, they mostly have no value. + '*/__pycache__/*', + // Remove pkgconfig + 'lib/pkgconfig*', + // Remove the static lib + 'lib/*.a' + ]; +end; + +function TPyEnvironmentProjectDeployPlatform.LocatePythonBundle: string; +begin + Result := TPath.Combine(GetBundleImageFolder(), GetPythonBundleName()); +end; + +class function TPyEnvironmentProjectDeployPlatform.GetDeployables( + const AProjectFileName, APythonEnvironmentDir, + APythonVersion: string): TArray; +begin + var LProjectDeploy := TPyEnvironmentProjectDeployPlatformClass(Self).Create( + AProjectFileName, APythonEnvironmentDir, APythonVersion); + try + Result := LProjectDeploy.Build(); + finally + LProjectDeploy.Free(); + end; +end; + +end. diff --git a/src/Project/IDE/Deploy/PyEnvironment.Project.IDE.Deploy.pas b/src/Project/IDE/Deploy/PyEnvironment.Project.IDE.Deploy.pas new file mode 100644 index 0000000..49c577e --- /dev/null +++ b/src/Project/IDE/Deploy/PyEnvironment.Project.IDE.Deploy.pas @@ -0,0 +1,233 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Deploy Python embeddables using project menu *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy; + +interface + +uses + System.Generics.Collections, + DeploymentAPI, + PyEnvironment.Project.IDE.Types; + +type + TPyEnvironmentProjectDeploy = class + strict private + class var + FDeployableFiles: TDictionary>; + FAbsolutePath: string; + FPath: string; + FPathChecked: Boolean; + class procedure FindPath(out APath, AAbsolutePath: string); static; + class function GetAbsolutePath: string; static; + class function GetFound: Boolean; static; + class function GetPath: string; static; + class function IsValidPythonEnvironmentDir(const APath: string): Boolean; static; + public + const + DEPLOYMENT_CLASS = 'Python'; + PROJECT_USE_PYTHON = 'PYTHON'; + PROJECT_NO_USE_PYTHON = 'NOPYTHON'; + PYTHON_ENVIRONMENT_DIR_VARIABLE = 'PYTHONENVIRONMENTDIR'; + PYTHON_VERSIONS: array[0..4] of string = ('3.8', '3.9', '3.10', '3.11', '3.12'); + SUPPORTED_PLATFORMS = [ + TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64, + TPyEnvironmentProjectPlatform.Android, TPyEnvironmentProjectPlatform.Android64, + TPyEnvironmentProjectPlatform.iOSSimARM64, TPyEnvironmentProjectPlatform.iOSDevice64, + TPyEnvironmentProjectPlatform.OSX64, TPyEnvironmentProjectPlatform.OSXARM64, + TPyEnvironmentProjectPlatform.Linux64]; + public + class constructor Create(); + class destructor Destroy(); + + class function GetDeployFiles(const AProjectName, APythonVersion: string; + const APlatform: TPyEnvironmentProjectPlatform) + : TArray; static; + + class property AbsolutePath: string read GetAbsolutePath; + class property Found: Boolean read GetFound; + class property Path: string read GetPath; + end; + +implementation + +uses + System.SysUtils, System.IOUtils, System.StrUtils, + PyEnvironment.Project.IDE.Helper, + PyEnvironment.Project.IDE.Deploy.Platform, + PyEnvironment.Project.IDE.Deploy.AndroidARM, + PyEnvironment.Project.IDE.Deploy.AndroidARM64, + PyEnvironment.Project.IDE.Deploy.OSX64, + PyEnvironment.Project.IDE.Deploy.OSXARM64, + PyEnvironment.Project.IDE.Deploy.iOSSimARM64, + PyEnvironment.Project.IDE.Deploy.iOSDevice64; + +{ TPyEnvironmentProject } + +class constructor TPyEnvironmentProjectDeploy.Create; +begin + FDeployableFiles := TDictionary>.Create(); + + FDeployableFiles.Add('3.7', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.7.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.7.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.7.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.8', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.8.10-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.8.10-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.8.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.9', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.9.13-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.9.13-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.9.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.10', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.10.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.10.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.10.9-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.11', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.11.2-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.11.2-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.11.2-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.12', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.12.0-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.12.0-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.12.0-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); +end; + +class destructor TPyEnvironmentProjectDeploy.Destroy; +begin + FDeployableFiles.Free(); +end; + +class procedure TPyEnvironmentProjectDeploy.FindPath(out APath, + AAbsolutePath: string); +begin + AAbsolutePath := TPyEnvironmentOTAHelper.GetEnvironmentVar(PYTHON_ENVIRONMENT_DIR_VARIABLE, True); + if IsValidPythonEnvironmentDir(AAbsolutePath) then + APath := '$(' + PYTHON_ENVIRONMENT_DIR_VARIABLE + ')' + else begin + APath := ''; + AAbsolutePath := ''; + end; +end; + +class function TPyEnvironmentProjectDeploy.GetAbsolutePath: string; +begin + if not FPathChecked then + GetPath(); + Result := FAbsolutePath; +end; + +class function TPyEnvironmentProjectDeploy.GetDeployFiles(const AProjectName, + APythonVersion: string; const APlatform: TPyEnvironmentProjectPlatform) +: TArray; +var + I: Integer; + LAllFiles: TArray; +begin + if not MatchText(APythonVersion, PYTHON_VERSIONS) then + Exit(nil); + + var LPlatformClass: TPyEnvironmentProjectDeployPlatformClass := nil; + case APlatform of + Android: LPlatformClass := TPyEnvironmentProjectDeployAndroidARM; + Android64: LPlatformClass := TPyEnvironmentProjectDeployAndroidARM64; + iOSDevice64: LPlatformClass := TPyEnvironmentProjectDeployIOSDevice64; + iOSSimARM64: LPlatformClass := TPyEnvironmentProjectDeployIOSSimARM64; + OSX64: LPlatformClass := TPyEnvironmentProjectDeployOSX64; + OSXARM64: LPlatformClass := TPyEnvironmentProjectDeployOSXARM64; + end; + + if Assigned(LPlatformClass) then + Exit(LPlatformClass.GetDeployables( + AProjectName, + GetAbsolutePath(), + APythonVersion + )); + + if not FDeployableFiles.ContainsKey(APythonVersion) then + Exit(nil); + + LAllFiles := FDeployableFiles[APythonVersion]; + for I := Low(LAllFiles) to High(LAllFiles) do + if LAllFiles[I].Platform = APlatform then + Result := Result + [LAllFiles[I]]; +end; + +class function TPyEnvironmentProjectDeploy.GetFound: Boolean; +begin + Result := not Path.IsEmpty; +end; + +class function TPyEnvironmentProjectDeploy.GetPath: string; +begin + if not FPathChecked then begin + FindPath(FPath, FAbsolutePath); + FPathChecked := True; + end; + Result := FPath; +end; + +class function TPyEnvironmentProjectDeploy.IsValidPythonEnvironmentDir( + const APath: string): Boolean; +begin + Result := TDirectory.Exists(APath); +end; + +end. diff --git a/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOS.pas b/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOS.pas new file mode 100644 index 0000000..27ed382 --- /dev/null +++ b/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOS.pas @@ -0,0 +1,360 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.iOS' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for iOS *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.iOS; + +interface + +uses + System.SysUtils, + System.Classes, + ToolsAPI, + DeploymentAPI, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.Platform; + +type + TPyEnvironmentProjectDeployIOS = class(TPyEnvironmentProjectDeployPlatform) + private + function GetFrameworksFolder(const APythonVersion: string): string; inline; + function GetExistingFrameworks: TArray; + private + /// + /// Create the frameworks template plist for the current project. + /// + function MakeFrameworkTemplatePlist: string; + /// + /// Create a framework for a single .dylib including a Info.plist for code sign. + /// + function MakeFramework(const ALibraryName: string; + const ACopyFileProc: TProc): TArray; + /// + /// Opens the Python distribution .zip collecting the .dylib files + /// to make frameworks. + /// + function MakeFrameworksFromZip: TArray; + /// + /// Delete all frameworks folders. + /// + procedure ClearFrameworks; + /// + /// The minimal Python bundle to be distributed to iOS. + /// Excludes many unnecessary files. + /// + function MakePythonMinimalBundle: string; + protected + function GetBundleMinimalIgnoresList: TArray; override; + function Build: TArray; override; + end; + +implementation + +uses + System.Masks, + System.IOUtils, + System.StrUtils, + System.Zip, + PyEnvironment.Project.IDE.Helper, + PyEnvironment.Project.IDE.Deploy; + +const + INFO_PLIST_TEMPLATE_NAME = 'info.plist.framework.TemplateiOS.xml'; + + FRAMEWORK_TEMPLATE_PLIST = + '' + sLineBreak + + '' + sLineBreak + + '' + sLineBreak + + '' + sLineBreak + + ' CFBundleDevelopmentRegion' + sLineBreak + + ' en' + sLineBreak + + ' CFBundleExecutable' + sLineBreak + + ' %CFBundleExecutable%' + sLineBreak + + ' CFBundleIdentifier' + sLineBreak + + ' %CFBundleIdentifier%' + sLineBreak + + ' CFBundleInfoDictionaryVersion' + sLineBreak + + ' 6.0' + sLineBreak + + ' CFBundlePackageType' + sLineBreak + + ' APPL' + sLineBreak + + ' CFBundleShortVersionString' + sLineBreak + + ' 1.0' + sLineBreak + + ' CFBundleSupportedPlatforms' + sLineBreak + + ' ' + sLineBreak + + ' iPhoneOS' + sLineBreak + + ' ' + sLineBreak + + ' MinimumOSVersion' + sLineBreak + + ' 12.0' + sLineBreak + + ' CFBundleVersion' + sLineBreak + + ' 1' + sLineBreak + + '' + sLineBreak + + ''; + +{ TPyEnvironmentProjectDeployIOS } + +function TPyEnvironmentProjectDeployIOS.GetFrameworksFolder( + const APythonVersion: string): string; +begin + // Deploy frameworks to the project's build folder + var LConfigFolder := (BorlandIDEServices as IOTAServices) + .ExpandRootMacro('$(Config)'); + + Result := TPath.Combine( + GetProjectFolder(), + GetPlatform().ToString(), + LConfigFolder, + TPath.GetFileNameWithoutExtension(ProjectFileName) + + '.' + + 'python' + APythonVersion + + '.' + + 'Frameworks'); +end; + +function TPyEnvironmentProjectDeployIOS.GetBundleMinimalIgnoresList: TArray; +begin + Result := inherited + [ + // We can't create a Python sub process. + 'bin/*', + // Remove the Python shared lib - this MUST be a framework + 'lib/*.dylib', + // Remove all shared libs + 'lib/python3.*/lib-dynload/*' + ]; +end; + +function TPyEnvironmentProjectDeployIOS.GetExistingFrameworks: TArray; +begin + Result := TDirectory.GetFiles(GetFrameworksFolder(PythonVersion), + TSearchOption.soAllDirectories, + function(const AFileName: string; const SearchRec: TSearchRec): boolean + begin + Result := String(SearchRec.Name).EndsWith('.dylib') or String(SearchRec.Name).EndsWith('.plist'); + end); +end; + +function TPyEnvironmentProjectDeployIOS.MakeFrameworkTemplatePlist: string; +begin + Result := TPath.Combine(GetProjectFolder(), INFO_PLIST_TEMPLATE_NAME); + if not TFile.Exists(Result) then + TFile.WriteAllText(Result, FRAMEWORK_TEMPLATE_PLIST, TEncoding.UTF8); +end; + +function TPyEnvironmentProjectDeployIOS.MakeFramework( + const ALibraryName: string; + const ACopyFileProc: TProc): TArray; +begin + // The bundle indentifier is base upon its base name. + var LBundleIdentifier := IfThen(ALibraryName.Contains('.'), ALibraryName.Split(['.'])[0], ALibraryName); + // The framework folder name. + var LFrameworkName := LBundleIdentifier + '.framework'; + // The framework folder located in the project root folder. + var LFrameworkFolder := TPath.Combine(GetFrameworksFolder(PythonVersion), LFrameworkName); + // The framework library name; this is the distributed .dylib file located + // in the framework folder. + var LLibraryName := TPath.Combine(LFrameworkFolder, ALibraryName); + // Frameworks requires plist. This is the template plist. + var LInfoPlistTemplateFileName := MakeFrameworkTemplatePlist(); + // The framework plist. It goes alongside .dylib + var LInfoPlistFileName := TPath.Combine(LFrameworkFolder, 'Info.plist'); + + // Create the framework directory + TDirectory.CreateDirectory(LFrameworkFolder); + + // Copy the framework dynamic library to the framework folder + var LStream := TFileStream.Create(LLibraryName, fmCreate); + try + ACopyFileProc(LStream); + finally + LStream.Free; + end; + + // Loads the Info.plist template + { TODO : Must rebuild info.plist with project's info } + var LBuff := TFile.ReadAllText(LInfoPlistTemplateFileName); // raise exception if template doesn't exist + LBuff := LBuff + .Replace('%CFBundleExecutable%', ALibraryName, []) + .Replace('%CFBundleIdentifier%', Format('%s.%s', [ + TPath.GetFileNameWithoutExtension(GetProjectName()), + LBundleIdentifier]), []); + // Creates the framework Info.plist file + TFile.WriteAllText(LInfoPlistFileName, LBuff, TEncoding.UTF8); + + // This is the framework files + Result := [LLibraryName, LInfoPlistFileName]; +end; + +function TPyEnvironmentProjectDeployIOS.MakeFrameworksFromZip: TArray; +begin + if TDirectory.Exists(GetFrameworksFolder(PythonVersion)) then + Exit(GetExistingFrameworks()); + + // It is the first time set or something has changed. Let's clean up. + ClearFrameworks(); + + var LZip := TZipFile.Create; + try + LZip.Open(LocatePythonBundle(), TZipMode.zmRead); + for var LFileName in LZip.FileNames do + begin + // We MUST generate frameworks for .dylib files + if not LFileName.EndsWith('.dylib') then + Continue; + // Remove test binary modules + if TPath.GetFileName(LFileName).Contains('_test') + or + TPath.GetFileName(LFileName).StartsWith('_xx') + or + TPath.GetFileName(LFileName).StartsWith('xx') then + Continue; + + Result := Result + MakeFramework(TPath.GetFileName(LFileName), + procedure(AStream: TStream) + var + LStream: TStream; + LLocalHeader: TZipHeader; + begin + LZip.Read(LFileName, LStream, LLocalHeader); + AStream.CopyFrom(LStream, LStream.Size); + end); + end; + LZip.Close; + finally + LZip.Free; + end; +end; + +procedure TPyEnvironmentProjectDeployIOS.ClearFrameworks; +begin + for var LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do begin + var LFrameworksFolder := GetFrameworksFolder(LPythonVersion); + if TDirectory.Exists(LFrameworksFolder) then + TDirectory.Delete(LFrameworksFolder, true); + end; +end; + +function TPyEnvironmentProjectDeployIOS.MakePythonMinimalBundle: string; + + function ShouldIgnore(const AFileName: string): boolean; + begin + for var LIgnore in GetBundleMinimalIgnoresList() do + if MatchesMask(AFileName, LIgnore) then + Exit(true); + + Result := false; + end; + + procedure ReadImage(const APythonBundle: string; const ACallback: TProc); + var + LStream: TStream; + LLocalHeader: TZipHeader; + begin + Assert(Assigned(ACallback), 'Parameter "ACallback" not assigned.'); + + var LZipReader := TZipFile.Create; + try + LZipReader.Open(APythonBundle, TZipMode.zmRead); + for var LFileName in LZipReader.FileNames do + begin + if ShouldIgnore(LFileName) then + Continue; + + LZipReader.Read(LFileName, LStream, LLocalHeader); + ACallback(LStream, LLocalHeader, LFileName); + end; + LZipReader.Close; + finally + LZipReader.Free; + end; + end; + +begin + // Create a minimal Python bundle + Result := GetBundleMinimalFileName(); + + // Always rebuild ??? Not now... + if TFile.Exists(Result) then + Exit(Result); + + if not TDirectory.Exists(TPath.GetDirectoryName(Result)) then + TDirectory.CreateDirectory(TPath.GetDirectoryName(Result)); + + var LZipWriter := TZipFile.Create(); + try + LZipWriter.Open(Result, TZipMode.zmWrite); + // Iterate over the image bundle and create the minimal bundle + ReadImage(LocatePythonBundle(), + procedure(AData: TStream; AZipHeader: TZipHeader; AFileName: TFileName) begin + // The minimal bundle does't have the /lib/pythonVER/ folder + var LFileName := String(AFileName) + .Replace('lib/', '', []) + .Replace('python' + PythonVersion + '/', '', []); + + if not LFileName.IsEmpty then + LZipWriter.Add( + AData, + LFileName); + end); + LZipWriter.Close; + finally + LZipWriter.Free; + end; +end; + +function TPyEnvironmentProjectDeployIOS.Build: TArray; +begin + // Add frameworks to the deploy file list + for var LFramework in MakeFrameworksFromZip() do + begin + var LDeployOp := TDeployOperation.doCopyOnly; + var LDirs := LFramework.Split(TPath.DirectorySeparatorChar); + var LFrameworkName := LDirs[High(LDirs) - 1]; + + if LFramework.EndsWith('.dylib') then + LDeployOp := TDeployOperation.doSetExecBit; + + Result := Result + [ + TPyEnvironmentDeployFile.Create(GetPlatform(), + // Make the framework path relative to project's path + LFramework.Replace(IncludeTrailingPathDelimiter(GetProjectFolder), '', []), + '.\Frameworks\' + LFrameworkName, + true, true, LDeployOp, '', false) + ]; + end; + + // Add the python minimal bundle to the deploy file list + var LPythonLibZip := MakePythonMinimalBundle(); + Result := Result + [ + TPyEnvironmentDeployFile.Create(GetPlatform(), + LPythonLibZip.Replace(IncludeTrailingPathDelimiter(PythonEnvironmentFolder), '', []), + // Extracts to ./PYVER by default + '.\' + PythonVersion, + false, true, TDeployOperation.doUnArchive, '') + ]; +end; + +end. diff --git a/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOSDevice64.pas b/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOSDevice64.pas new file mode 100644 index 0000000..50568b9 --- /dev/null +++ b/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOSDevice64.pas @@ -0,0 +1,72 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.iOS' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for iOS Device ARM64 *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.iOSDevice64; + +interface + +uses + System.SysUtils, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.iOS; + +type + TPyEnvironmentProjectDeployIOSDevice64 = class(TPyEnvironmentProjectDeployIOS) + protected + function GetPlatform: TPyEnvironmentProjectPlatform; override; + function GetPythonBundleName: string; override; + end; + +implementation + +uses + System.StrUtils; + +{ TPyEnvironmentProjectDeployIOSDevice64 } + +function TPyEnvironmentProjectDeployIOSDevice64.GetPlatform: TPyEnvironmentProjectPlatform; +begin + Result := TPyEnvironmentProjectPlatform.iOSDevice64; +end; + +function TPyEnvironmentProjectDeployIOSDevice64.GetPythonBundleName: string; +begin + case IndexStr(PythonVersion, ['3.8', '3.9', '3.10', '3.11', '3.12']) of + 0: Result := 'python3-ios-3.8.18-iphoneos.arm64.zip'; + 1: Result := 'python3-ios-3.9.18-iphoneos.arm64.zip'; + 2: Result := 'python3-ios-3.10.13-iphoneos.arm64.zip'; + 3: Result := 'python3-ios-3.11.6-iphoneos.arm64.zip'; + 4: Result := 'python3-ios-3.12.0-iphoneos.arm64.zip'; + else + Result := String.Empty; + end; +end; + +end. diff --git a/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOSSimARM64.pas b/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOSSimARM64.pas new file mode 100644 index 0000000..12b6af4 --- /dev/null +++ b/src/Project/IDE/Deploy/iOS/PyEnvironment.Project.IDE.Deploy.iOSSimARM64.pas @@ -0,0 +1,72 @@ +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy.iOS' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Make deployables for iOS ARM64 Simulator *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy.iOSSimARM64; + +interface + +uses + System.SysUtils, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.Deploy.iOS; + +type + TPyEnvironmentProjectDeployIOSSimARM64 = class(TPyEnvironmentProjectDeployIOS) + protected + function GetPlatform: TPyEnvironmentProjectPlatform; override; + function GetPythonBundleName: string; override; + end; + +implementation + +uses + System.StrUtils; + +{ TPyEnvironmentProjectDeployIOSSimARM64 } + +function TPyEnvironmentProjectDeployIOSSimARM64.GetPlatform: TPyEnvironmentProjectPlatform; +begin + Result := TPyEnvironmentProjectPlatform.iOSSimARM64; +end; + +function TPyEnvironmentProjectDeployIOSSimARM64.GetPythonBundleName: string; +begin + case IndexStr(PythonVersion, ['3.8', '3.9', '3.10', '3.11', '3.12']) of + 0: Result := 'python3-ios-3.8.18-iphonesimulator.arm64.zip'; + 1: Result := 'python3-ios-3.9.18-iphonesimulator.arm64.zip'; + 2: Result := 'python3-ios-3.10.13-iphonesimulator.arm64.zip'; + 3: Result := 'python3-ios-3.11.6-iphonesimulator.arm64.zip'; + 4: Result := 'python3-ios-3.12.0-iphonesimulator.arm64.zip'; + else + Result := String.Empty; + end; +end; + +end. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas index beff277..49c577e 100644 --- a/src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas @@ -1,288 +1,233 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Project.IDE.Deploy' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Deploy Python embeddables using project menu *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Project.IDE.Deploy; - -interface - -uses - System.Generics.Collections, - DeploymentAPI, - PyEnvironment.Project.IDE.Types; - -type - TPyEnvironmentProjectDeploy = class - strict private - class var - FDeployableFiles: TDictionary>; - FAbsolutePath: string; - FPath: string; - FPathChecked: Boolean; - class procedure FindPath(out APath, AAbsolutePath: string); static; - class function GetAbsolutePath: string; static; - class function GetFound: Boolean; static; - class function GetPath: string; static; - class function IsValidPythonEnvironmentDir(const APath: string): Boolean; static; - public - const - DEPLOYMENT_CLASS = 'Python'; - PROJECT_USE_PYTHON = 'PYTHON'; - PROJECT_NO_USE_PYTHON = 'NOPYTHON'; - PYTHON_ENVIRONMENT_DIR_VARIABLE = 'PYTHONENVIRONMENTDIR'; - PYTHON_VERSIONS: array[0..4] of string = ('3.7', '3.8', '3.9', '3.10', '3.11'); - SUPPORTED_PLATFORMS = [ - TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64, - TPyEnvironmentProjectPlatform.Android, TPyEnvironmentProjectPlatform.Android64, - //TPyEnvironmentProjectPlatform.iOSDevice64, - TPyEnvironmentProjectPlatform.OSX64, TPyEnvironmentProjectPlatform.OSXARM64, - TPyEnvironmentProjectPlatform.Linux64]; - public - class constructor Create(); - class destructor Destroy(); - - class function GetDeployFiles(const APythonVersion: string; const APlatform: TPyEnvironmentProjectPlatform): TArray; static; - class property AbsolutePath: string read GetAbsolutePath; - class property Found: Boolean read GetFound; - class property Path: string read GetPath; - end; - -implementation - -uses - System.SysUtils, System.IOUtils, - PyEnvironment.Project.IDE.Helper; - -{ TPyEnvironmentProject } - -class constructor TPyEnvironmentProjectDeploy.Create; -begin - FDeployableFiles := TDictionary>.Create(); - - // TIP -> RUN the "provide_deliverables.py" Python script to automate the following commands to the "deliverables_cmds.txt" file and copy/paste it here. - - FDeployableFiles.Add('3.7', [ - //Windows-win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.7.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Windows-amd64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.7.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Android-arm - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.7.16-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.16\arm\libpython3.7.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.16\arm\libpythonlauncher3.7.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - //Android-arm64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.7.16-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.16\arm64\libpython3.7.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.16\arm64\libpythonlauncher3.7.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.7.16-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.16\arm\libpython3.7.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.16\arm\libpythonlauncher3.7.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - //Macos-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.7.16-x86_64.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.7.16\intel\libpython3.7.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.7.16\intel\python3.7','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Linux-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.7.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') - ]); - - FDeployableFiles.Add('3.8', [ - //Windows-win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.8.10-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Windows-amd64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.8.10-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Android-arm - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.8.16-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.16\arm\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.16\arm\libpythonlauncher3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - //Android-arm64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.8.16-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.16\arm64\libpython3.8.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.16\arm64\libpythonlauncher3.8.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.8.16-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.16\arm\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.16\arm\libpythonlauncher3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - //Macos-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.8.16-x86_64.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.8.16\intel\libpython3.8.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.8.16\intel\python3.8','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Macos-universal2 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.8.16-universal2.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.8.16\arm\libpython3.8.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.8.16\arm\python3.8','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Linux-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.8.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') - ]); - - FDeployableFiles.Add('3.9', [ - //Windows-win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.9.13-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Windows-amd64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.9.13-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Android-arm - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.9.16-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.16\arm\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.16\arm\libpythonlauncher3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - //Android-arm64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.9.16-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.16\arm64\libpython3.9.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.16\arm64\libpythonlauncher3.9.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.9.16-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.16\arm\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.16\arm\libpythonlauncher3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - //Macos-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.9.16-x86_64.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.9.16\intel\libpython3.9.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.9.16\intel\python3.9','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Macos-universal2 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.9.16-universal2.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.9.16\arm\libpython3.9.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.9.16\arm\python3.9','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Linux-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.9.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') - ]); - - FDeployableFiles.Add('3.10', [ - //Windows-win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.10.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Windows-amd64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.10.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Android-arm - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.10.7-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.7\arm\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.7\arm\libpythonlauncher3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - //Android-arm64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.10.7-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.7\arm64\libpython3.10.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.7\arm64\libpythonlauncher3.10.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.10.7-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.7\arm\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.7\arm\libpythonlauncher3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - //Macos-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.10.9-x86_64.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.10.9\intel\libpython3.10.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.10.9\intel\python3.10','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Macos-universal2 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.10.9-universal2.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.10.9\arm\libpython3.10.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.10.9\arm\python3.10','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Linux-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.10.9-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') - ]); - - FDeployableFiles.Add('3.11', [ - //Windows-win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.11.2-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Windows-amd64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.11.2-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), - //Android-arm - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.11.2-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.11.2\arm\libpython3.11.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.11.2\arm\libpythonlauncher3.11.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), - //Android-arm64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.11.2-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.11.2\arm64\libpython3.11.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.11.2\arm64\libpythonlauncher3.11.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.11.2-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.11.2\arm\libpython3.11.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.11.2\arm\libpythonlauncher3.11.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), - //Macos-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.11.2-x86_64.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.11.2\intel\libpython3.11.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\macos\3.11.2\intel\python3.11','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Macos-universal2 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.11.2-universal2.zip', 'Contents\Resources\', True, True, TDeployOperation.doCopyOnly, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.11.2\arm\libpython3.11.dylib','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\macos\3.11.2\arm\python3.11','Contents\MacOS\', True, True, TDeployOperation.doSetExecBit, ''), - //Linux-x86_64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.11.2-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') - ]); -end; - -class destructor TPyEnvironmentProjectDeploy.Destroy; -begin - FDeployableFiles.Free(); -end; - -class procedure TPyEnvironmentProjectDeploy.FindPath(out APath, - AAbsolutePath: string); -begin - AAbsolutePath := TPyEnvironmentOTAHelper.GetEnvironmentVar(PYTHON_ENVIRONMENT_DIR_VARIABLE, True); - if IsValidPythonEnvironmentDir(AAbsolutePath) then - APath := '$(' + PYTHON_ENVIRONMENT_DIR_VARIABLE + ')' - else begin - APath := ''; - AAbsolutePath := ''; - end; -end; - -class function TPyEnvironmentProjectDeploy.GetAbsolutePath: string; -begin - if not FPathChecked then - GetPath(); - Result := FAbsolutePath; -end; - -class function TPyEnvironmentProjectDeploy.GetDeployFiles(const APythonVersion: string; - const APlatform: TPyEnvironmentProjectPlatform): TArray; -var - I: Integer; - LAllFiles: TArray; -begin - Result := []; - - if not FDeployableFiles.ContainsKey(APythonVersion) then - Exit(); - - LAllFiles := FDeployableFiles[APythonVersion]; - for I := Low(LAllFiles) to High(LAllFiles) do - if LAllFiles[I].Platform = APlatform then - Result := Result + [LAllFiles[I]]; -end; - -class function TPyEnvironmentProjectDeploy.GetFound: Boolean; -begin - Result := not Path.IsEmpty; -end; - -class function TPyEnvironmentProjectDeploy.GetPath: string; -begin - if not FPathChecked then begin - FindPath(FPath, FAbsolutePath); - FPathChecked := True; - end; - Result := FPath; -end; - -class function TPyEnvironmentProjectDeploy.IsValidPythonEnvironmentDir( - const APath: string): Boolean; -begin - Result := TDirectory.Exists(APath); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Deploy Python embeddables using project menu *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Deploy; + +interface + +uses + System.Generics.Collections, + DeploymentAPI, + PyEnvironment.Project.IDE.Types; + +type + TPyEnvironmentProjectDeploy = class + strict private + class var + FDeployableFiles: TDictionary>; + FAbsolutePath: string; + FPath: string; + FPathChecked: Boolean; + class procedure FindPath(out APath, AAbsolutePath: string); static; + class function GetAbsolutePath: string; static; + class function GetFound: Boolean; static; + class function GetPath: string; static; + class function IsValidPythonEnvironmentDir(const APath: string): Boolean; static; + public + const + DEPLOYMENT_CLASS = 'Python'; + PROJECT_USE_PYTHON = 'PYTHON'; + PROJECT_NO_USE_PYTHON = 'NOPYTHON'; + PYTHON_ENVIRONMENT_DIR_VARIABLE = 'PYTHONENVIRONMENTDIR'; + PYTHON_VERSIONS: array[0..4] of string = ('3.8', '3.9', '3.10', '3.11', '3.12'); + SUPPORTED_PLATFORMS = [ + TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64, + TPyEnvironmentProjectPlatform.Android, TPyEnvironmentProjectPlatform.Android64, + TPyEnvironmentProjectPlatform.iOSSimARM64, TPyEnvironmentProjectPlatform.iOSDevice64, + TPyEnvironmentProjectPlatform.OSX64, TPyEnvironmentProjectPlatform.OSXARM64, + TPyEnvironmentProjectPlatform.Linux64]; + public + class constructor Create(); + class destructor Destroy(); + + class function GetDeployFiles(const AProjectName, APythonVersion: string; + const APlatform: TPyEnvironmentProjectPlatform) + : TArray; static; + + class property AbsolutePath: string read GetAbsolutePath; + class property Found: Boolean read GetFound; + class property Path: string read GetPath; + end; + +implementation + +uses + System.SysUtils, System.IOUtils, System.StrUtils, + PyEnvironment.Project.IDE.Helper, + PyEnvironment.Project.IDE.Deploy.Platform, + PyEnvironment.Project.IDE.Deploy.AndroidARM, + PyEnvironment.Project.IDE.Deploy.AndroidARM64, + PyEnvironment.Project.IDE.Deploy.OSX64, + PyEnvironment.Project.IDE.Deploy.OSXARM64, + PyEnvironment.Project.IDE.Deploy.iOSSimARM64, + PyEnvironment.Project.IDE.Deploy.iOSDevice64; + +{ TPyEnvironmentProject } + +class constructor TPyEnvironmentProjectDeploy.Create; +begin + FDeployableFiles := TDictionary>.Create(); + + FDeployableFiles.Add('3.7', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.7.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.7.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.7.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.8', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.8.10-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.8.10-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.8.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.9', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.9.13-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.9.13-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.9.16-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.10', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.10.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.10.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.10.9-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.11', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.11.2-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.11.2-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.11.2-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); + + FDeployableFiles.Add('3.12', [ + //Windows-win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.12.0-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Windows-amd64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.12.0-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), + //Linux-x86_64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.12.0-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') + ]); +end; + +class destructor TPyEnvironmentProjectDeploy.Destroy; +begin + FDeployableFiles.Free(); +end; + +class procedure TPyEnvironmentProjectDeploy.FindPath(out APath, + AAbsolutePath: string); +begin + AAbsolutePath := TPyEnvironmentOTAHelper.GetEnvironmentVar(PYTHON_ENVIRONMENT_DIR_VARIABLE, True); + if IsValidPythonEnvironmentDir(AAbsolutePath) then + APath := '$(' + PYTHON_ENVIRONMENT_DIR_VARIABLE + ')' + else begin + APath := ''; + AAbsolutePath := ''; + end; +end; + +class function TPyEnvironmentProjectDeploy.GetAbsolutePath: string; +begin + if not FPathChecked then + GetPath(); + Result := FAbsolutePath; +end; + +class function TPyEnvironmentProjectDeploy.GetDeployFiles(const AProjectName, + APythonVersion: string; const APlatform: TPyEnvironmentProjectPlatform) +: TArray; +var + I: Integer; + LAllFiles: TArray; +begin + if not MatchText(APythonVersion, PYTHON_VERSIONS) then + Exit(nil); + + var LPlatformClass: TPyEnvironmentProjectDeployPlatformClass := nil; + case APlatform of + Android: LPlatformClass := TPyEnvironmentProjectDeployAndroidARM; + Android64: LPlatformClass := TPyEnvironmentProjectDeployAndroidARM64; + iOSDevice64: LPlatformClass := TPyEnvironmentProjectDeployIOSDevice64; + iOSSimARM64: LPlatformClass := TPyEnvironmentProjectDeployIOSSimARM64; + OSX64: LPlatformClass := TPyEnvironmentProjectDeployOSX64; + OSXARM64: LPlatformClass := TPyEnvironmentProjectDeployOSXARM64; + end; + + if Assigned(LPlatformClass) then + Exit(LPlatformClass.GetDeployables( + AProjectName, + GetAbsolutePath(), + APythonVersion + )); + + if not FDeployableFiles.ContainsKey(APythonVersion) then + Exit(nil); + + LAllFiles := FDeployableFiles[APythonVersion]; + for I := Low(LAllFiles) to High(LAllFiles) do + if LAllFiles[I].Platform = APlatform then + Result := Result + [LAllFiles[I]]; +end; + +class function TPyEnvironmentProjectDeploy.GetFound: Boolean; +begin + Result := not Path.IsEmpty; +end; + +class function TPyEnvironmentProjectDeploy.GetPath: string; +begin + if not FPathChecked then begin + FindPath(FPath, FAbsolutePath); + FPathChecked := True; + end; + Result := FPath; +end; + +class function TPyEnvironmentProjectDeploy.IsValidPythonEnvironmentDir( + const APath: string): Boolean; +begin + Result := TDirectory.Exists(APath); +end; + +end. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas index 1778928..703922b 100644 --- a/src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas @@ -1,802 +1,811 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Project.IDE.Helper' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Helpers for the Python project menu *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Project.IDE.Helper; - -interface - -uses - System.Classes, - ToolsAPI, DeploymentAPI, - PyEnvironment.Project.IDE.Types; - -type - TPyEnvironmentProjectHelper = record - strict private - const - PYTHON_VERSION_PREFIX = 'PYTHONVER'; - class function ContainsStringInArray(const AString: string; const AArray: TArray): Boolean; static; inline; - class function GetIsPyEnvironmentDefined( - const AProject: IOTAProject): Boolean; static; - class procedure SetIsPyEnvironmentDefined(const AProject: IOTAProject; - const AValue: Boolean); static; - class function GetCurrentPythonVersion( - const AProject: IOTAProject): string; static; - class procedure SetCurrentPythonVersion(const AProject: IOTAProject; - const AValue: string); static; - class function BuildPythonVersionConditional(const APythonVersion: string): string; static; - public - class procedure AddDeployFile(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const ADeployFile: TPyEnvironmentDeployFile); static; - class procedure RemoveDeployFile(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const APlatform: TPyEnvironmentProjectPlatform; ALocalFileName: string; const ARemoteDir: string); static; - class procedure RemoveDeployFilesOfClass(const AProject: IOTAProject); overload; static; - class procedure RemoveDeployFilesOfClass(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const APlatform: TPyEnvironmentProjectPlatform); overload; static; - class procedure RemoveUnexpectedDeployFilesOfClass(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const APlatform: TPyEnvironmentProjectPlatform; const AAllowedFiles: TArray); static; - - class function IsPyEnvironmentDefinedForPlatform(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig): Boolean; static; - class function SupportsEnvironmentDeployment(const AProject: IOTAProject): Boolean; static; - - class property IsPyEnvironmentDefined[const AProject: IOTAProject]: Boolean read GetIsPyEnvironmentDefined write SetIsPyEnvironmentDefined; - class property CurrentPythonVersion[const AProject: IOTAProject]: string read GetCurrentPythonVersion write SetCurrentPythonVersion; - end; - - TPyEnvironmentOTAHelper = record - strict private - const - DefaultOptionsSeparator = ';'; - OutputDirPropertyName = 'OutputDir'; - class function ExpandConfiguration(const ASource: string; const AConfig: IOTABuildConfiguration): string; static; - class function ExpandEnvironmentVar(var AValue: string): Boolean; static; - class function ExpandOutputPath(const ASource: string; const ABuildConfig: IOTABuildConfiguration): string; static; - class function ExpandPath(const ABaseDir, ARelativeDir: string): string; static; - class function ExpandVars(const ASource: string): string; static; - class function GetEnvironmentVars(const AVars: TStrings; AExpand: Boolean): Boolean; static; - class function GetProjectOptionsConfigurations(const AProject: IOTAProject): IOTAProjectOptionsConfigurations; static; - class procedure MultiSzToStrings(const ADest: TStrings; const ASource: PChar); static; - class procedure StrResetLength(var S: string); static; - class function TryGetProjectOutputPath(const AProject: IOTAProject; ABuildConfig: IOTABuildConfiguration; out AOutputPath: string): Boolean; overload; static; - public - class function ContainsOptionValue(const AValues, AValue: string; const ASeparator: string = DefaultOptionsSeparator): Boolean; static; - class function InsertOptionValue(const AValues, AValue: string; const ASeparator: string = DefaultOptionsSeparator): string; static; - class function RemoveOptionValue(const AValues, AValue: string; const ASeparator: string = DefaultOptionsSeparator): string; static; - - class function GetEnvironmentVar(const AName: string; AExpand: Boolean): string; static; - class function TryCopyFileToOutputPath(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; const AFileName: string): Boolean; static; - class function TryCopyFileToOutputPathOfActiveBuild(const AProject: IOTAProject; const AFileName: string): Boolean; static; - class function TryGetBuildConfig(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; out ABuildConfig: IOTABuildConfiguration): Boolean; static; - class function TryGetProjectOutputPath(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; out AOutputPath: string): Boolean; overload; static; - class function TryGetProjectOutputPathOfActiveBuild(const AProject: IOTAProject; out AOutputPath: string): Boolean; static; - class function TryRemoveOutputFile(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; AFileName: string): Boolean; static; - class function TryRemoveOutputFileOfActiveBuild(const AProject: IOTAProject; const AFileName: string): Boolean; static; - end; - -implementation - -uses - System.SysUtils, System.IOUtils, System.Generics.Collections, - DccStrs, - Winapi.Windows, Winapi.ShLwApi, - PyEnvironment.Project.IDE.Deploy; - -{ TPyEnvironmentProjectHelper } - -class procedure TPyEnvironmentProjectHelper.AddDeployFile( - const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; - const ADeployFile: TPyEnvironmentDeployFile); -type - TDeployFileExistence = (DoesNotExist, AlreadyExists, NeedReplaced); - - function GetDeployFileExistence(const AProjectDeployment: IProjectDeployment; - const ALocalFileName, ARemoteDir, APlatformName, AConfigName: string): TDeployFileExistence; - var - LRemoteFileName: string; - LFile: IProjectDeploymentFile; - LFiles: TDictionary.TValueCollection; - begin - Result := TDeployFileExistence.DoesNotExist; - LRemoteFileName := TPath.Combine(ARemoteDir, TPath.GetFileName(ALocalFileName)); - LFiles := AProjectDeployment.Files; - if Assigned(LFiles) then begin - for LFile in LFiles do begin - if (LFile.FilePlatform = APlatformName) and (LFile.Configuration = AConfigName) then begin - if SameText(LRemoteFileName, TPath.Combine(LFile.RemoteDir[APlatformName], LFile.RemoteName[APlatformName])) then begin - if (LFile.LocalName = ALocalFileName) and (LFile.DeploymentClass = TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) and - (LFile.Condition = ADeployFile.Condition) and (LFile.Operation[APlatformName] = ADeployFile.Operation) and - LFile.Enabled[APlatformName] and LFile.Overwrite[APlatformName] and - (LFile.Required = ADeployFile.Required) and (Result = TDeployFileExistence.DoesNotExist) then - begin - Result := TDeployFileExistence.AlreadyExists; - end else - Exit(TDeployFileExistence.NeedReplaced); - end; - end; - end; - end; - end; - - function CanDeployFile(const ADeployFileExistence: TDeployFileExistence): boolean; - begin - Result := (ADeployFileExistence in [TDeployFileExistence.NeedReplaced, - TDeployFileExistence.DoesNotExist]) - and (AConfig in ADeployFile.Configs) - end; - - procedure DoAddDeployFile(const AProjectDeployment: IProjectDeployment; - const ALocalFileName, APlatformName, AConfigName: string); - var - LFile: IProjectDeploymentFile; - begin - LFile := AProjectDeployment.CreateFile(AConfigName, APlatformName, ALocalFileName); - if Assigned(LFile) then begin - LFile.Overwrite[APlatformName] := True; - LFile.Enabled[APlatformName] := True; - LFile.Required := ADeployFile.Required; - LFile.Condition := ADeployFile.Condition; - LFile.Operation[APlatformName] := ADeployFile.Operation; - LFile.RemoteDir[APlatformName] := ADeployFile.RemotePath; - LFile.DeploymentClass := TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS; - LFile.RemoteName[APlatformName] := TPath.GetFileName(ALocalFileName); - AProjectDeployment.AddFile(AConfigName, APlatformName, LFile); - end; - end; - -var - LProjectDeployment: IProjectDeployment; - LConfigName: string; - LPlatformName: string; - LLocalFileName: string; - LDeployFileExistence: TDeployFileExistence; -begin - if (ADeployFile.LocalFileName <> '') and Supports(AProject, IProjectDeployment, LProjectDeployment) then begin - LConfigName := AConfig.ToString; - LPlatformName := ADeployFile.Platform.ToString; - LLocalFileName := TPath.Combine(TPyEnvironmentProjectDeploy.Path, ADeployFile.LocalFileName); - LDeployFileExistence := GetDeployFileExistence(LProjectDeployment, LLocalFileName, ADeployFile.RemotePath, LPlatformName, LConfigName); - if LDeployFileExistence = TDeployFileExistence.NeedReplaced then - RemoveDeployFile(AProject, AConfig, ADeployFile.Platform, ADeployFile.LocalFileName, ADeployFile.RemotePath); - if CanDeployFile(LDeployFileExistence) then - DoAddDeployFile(LProjectDeployment, LLocalFileName, LPlatformName, LConfigName); - end; -end; - -class function TPyEnvironmentProjectHelper.BuildPythonVersionConditional( - const APythonVersion: string): string; -begin - Result := PYTHON_VERSION_PREFIX + APythonVersion.Replace('.', '', [rfReplaceAll]); -end; - -class function TPyEnvironmentProjectHelper.ContainsStringInArray( - const AString: string; const AArray: TArray): Boolean; -var - I: Integer; -begin - Result := False; - for I := Low(AArray) to High(AArray) do - if AArray[I] = AString then - Exit(True); -end; - -class function TPyEnvironmentProjectHelper.GetCurrentPythonVersion( - const AProject: IOTAProject): string; -var - LBaseConfiguration: IOTABuildConfiguration; - LOptionsConfigurations: IOTAProjectOptionsConfigurations; - LPythonVersion: string; -begin - if not (Assigned(AProject) - and Supports(AProject.ProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations) - and Assigned(LOptionsConfigurations.BaseConfiguration)) then - Exit(String.Empty); - - LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; - if Assigned(LBaseConfiguration) then begin - for LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do begin - if TPyEnvironmentOTAHelper.ContainsOptionValue( - LBaseConfiguration.Value[sDefine], - BuildPythonVersionConditional(LPythonVersion)) then - Exit(LPythonVersion); - end; - end; - - Result := String.Empty; -end; - -class function TPyEnvironmentProjectHelper.GetIsPyEnvironmentDefined( - const AProject: IOTAProject): Boolean; -var - LBaseConfiguration: IOTABuildConfiguration; - LOptionsConfigurations: IOTAProjectOptionsConfigurations; -begin - Result := Assigned(AProject) - and Supports(AProject.ProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations); - - if Result then begin - LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; - Result := Assigned(LBaseConfiguration) - and TPyEnvironmentOTAHelper.ContainsOptionValue( - LBaseConfiguration.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_USE_PYTHON); - end; -end; - -class function TPyEnvironmentProjectHelper.IsPyEnvironmentDefinedForPlatform( - const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; - const AConfig: TPyEnvironmentProjectConfig): Boolean; -var - LBuildConfig: IOTABuildConfiguration; -begin - Assert(IsPyEnvironmentDefined[AProject]); - Result := TPyEnvironmentOTAHelper.TryGetBuildConfig( - AProject, APlatform, AConfig, LBuildConfig) - and not TPyEnvironmentOTAHelper.ContainsOptionValue( - LBuildConfig.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_NO_USE_PYTHON); -end; - -class procedure TPyEnvironmentProjectHelper.RemoveDeployFile( - const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; - const APlatform: TPyEnvironmentProjectPlatform; ALocalFileName: string; - const ARemoteDir: string); -var - LProjectDeployment: IProjectDeployment; - LFiles: TDictionary.TValueCollection; - LFile: IProjectDeploymentFile; - LRemoteFileName: string; - LRemoveFiles: TArray; -begin - if (ALocalFileName <> '') and Supports(AProject, IProjectDeployment, LProjectDeployment) then begin - ALocalFileName := TPath.Combine(TPyEnvironmentProjectDeploy.Path, ALocalFileName); - LProjectDeployment.RemoveFile(AConfig.ToString, APlatform.ToString, ALocalFileName); - LFiles := LProjectDeployment.Files; - if Assigned(LFiles) then begin - LRemoteFileName := TPath.Combine(ARemoteDir, TPath.GetFileName(ALocalFileName)); - LRemoveFiles := []; - for LFile in LFiles do - if SameText(LRemoteFileName, TPath.Combine(LFile.RemoteDir[APlatform.ToString], LFile.RemoteName[APlatform.ToString])) then - LRemoveFiles := LRemoveFiles + [LFile]; - for LFile in LRemoveFiles do - LProjectDeployment.RemoveFile(AConfig.ToString, APlatform.ToString, LFile.LocalName); - end; - end; -end; - -class procedure TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass( - const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; - const APlatform: TPyEnvironmentProjectPlatform); -var - LProjectDeployment: IProjectDeployment; - LFile: IProjectDeploymentFile; - LConfigName: string; - LPlatformName: string; -begin - if Supports(AProject, IProjectDeployment, LProjectDeployment) then begin - LConfigName := AConfig.ToString; - LPlatformName := APlatform.ToString; - for LFile in LProjectDeployment.GetFilesOfClass(TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) do - if (LFile.Configuration = LConfigName) and ContainsStringInArray(LPlatformName, LFile.Platforms) then - LProjectDeployment.RemoveFile(LConfigName, LPlatformName, LFile.LocalName); - end; -end; - -class procedure TPyEnvironmentProjectHelper.RemoveUnexpectedDeployFilesOfClass( - const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; - const APlatform: TPyEnvironmentProjectPlatform; - const AAllowedFiles: TArray); - - function IsAllowedFile(const AFile: IProjectDeploymentFile; const APlatformName: string): Boolean; - var - LDeployFile: TPyEnvironmentDeployFile; - begin - Result := False; - for LDeployFile in AAllowedFiles do begin - if (AFile.LocalName = LDeployFile.LocalFileName) and SameText(AFile.RemoteDir[APlatformName], LDeployFile.RemotePath) and - SameText(AFile.RemoteName[APlatformName], TPath.GetFileName(LDeployFile.LocalFileName)) and - (AFile.DeploymentClass = TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) and - (AFile.Condition = LDeployFile.Condition) and (AFile.Operation[APlatformName] = LDeployFile.Operation) and - AFile.Enabled[APlatformName] and AFile.Overwrite[APlatformName] and - (AFile.Required = LDeployFile.Required) then - begin - Exit(True); - end; - end; - end; - -var - LProjectDeployment: IProjectDeployment; - LFile: IProjectDeploymentFile; - LConfigName: string; - LPlatformName: string; -begin - if Supports(AProject, IProjectDeployment, LProjectDeployment) then begin - LConfigName := AConfig.ToString; - LPlatformName := APlatform.ToString; - for LFile in LProjectDeployment.GetFilesOfClass(TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) do begin - if (LFile.Configuration = LConfigName) and ContainsStringInArray(LPlatformName, LFile.Platforms) and - not IsAllowedFile(LFile, LPlatformName) then - begin - LProjectDeployment.RemoveFile(LConfigName, LPlatformName, LFile.LocalName); - end; - end; - end; -end; - -class procedure TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass( - const AProject: IOTAProject); -var - LProjectDeployment: IProjectDeployment; -begin - if Supports(AProject, IProjectDeployment, LProjectDeployment) then - LProjectDeployment.RemoveFilesOfClass(TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS); -end; - -class procedure TPyEnvironmentProjectHelper.SetCurrentPythonVersion( - const AProject: IOTAProject; const AValue: string); -var - LProjectOptions: IOTAProjectOptions; - LOptionsConfigurations: IOTAProjectOptionsConfigurations; - LBaseConfiguration: IOTABuildConfiguration; - LPythonVersion: string; -begin - if Assigned(AProject) then begin - LProjectOptions := AProject.ProjectOptions; - if Assigned(LProjectOptions) then begin - if Supports(LProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations) then begin - LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; - if Assigned(LBaseConfiguration) then begin - LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; - for LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do - LBaseConfiguration.Value[sDefine] := - TPyEnvironmentOTAHelper.RemoveOptionValue( - LBaseConfiguration.Value[sDefine], - BuildPythonVersionConditional(LPythonVersion)); - - if not AValue.IsEmpty() then - LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper - .InsertOptionValue(LBaseConfiguration.Value[sDefine], - BuildPythonVersionConditional(AValue)); - end; - end; - LProjectOptions.ModifiedState := True; - end; - end; -end; - -class procedure TPyEnvironmentProjectHelper.SetIsPyEnvironmentDefined( - const AProject: IOTAProject; const AValue: Boolean); -var - LProjectOptions: IOTAProjectOptions; - LOptionsConfigurations: IOTAProjectOptionsConfigurations; - LBaseConfiguration: IOTABuildConfiguration; -begin - if Assigned(AProject) then begin - LProjectOptions := AProject.ProjectOptions; - if Assigned(LProjectOptions) then begin - if Supports(LProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations) then begin - LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; - if Assigned(LBaseConfiguration) then begin - if AValue then - LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper - .InsertOptionValue( - LBaseConfiguration.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_USE_PYTHON) - else - LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper - .RemoveOptionValue( - LBaseConfiguration.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_USE_PYTHON); - end; - end; - LProjectOptions.ModifiedState := True; - end; - end; -end; - -class function TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment( - const AProject: IOTAProject): Boolean; -begin - Result := Assigned(AProject) - and AProject.FileName.EndsWith('.dproj', True) - and ((AProject.ApplicationType = sApplication) - or - (AProject.ApplicationType = sConsole)); -end; - -{ TPyEnvironmentOTAHelper } - -class function TPyEnvironmentOTAHelper.ContainsOptionValue(const AValues, - AValue, ASeparator: string): Boolean; -var - LValues: TArray; - I: Integer; -begin - LValues := AValues.Split([ASeparator], TStringSplitOptions.None); - for I := 0 to Length(LValues) - 1 do - if SameText(LValues[I], AValue) then - Exit(True); - Result := False; -end; - -class function TPyEnvironmentOTAHelper.ExpandConfiguration( - const ASource: string; const AConfig: IOTABuildConfiguration): string; -begin - Result := StringReplace(ASource, '$(Platform)', AConfig.Platform, [rfReplaceAll, rfIgnoreCase]); - Result := StringReplace(Result, '$(Config)', AConfig.Name, [rfReplaceAll, rfIgnoreCase]); -end; - -class function TPyEnvironmentOTAHelper.ExpandEnvironmentVar( - var AValue: string): Boolean; -var - R: Integer; - LExpanded: string; -begin - SetLength(LExpanded, 1); - R := ExpandEnvironmentStrings(PChar(AValue), PChar(LExpanded), 0); - SetLength(LExpanded, R); - Result := ExpandEnvironmentStrings(PChar(AValue), PChar(LExpanded), R) <> 0; - if Result then begin - StrResetLength(LExpanded); - AValue := LExpanded; - end; -end; - -class function TPyEnvironmentOTAHelper.ExpandOutputPath(const ASource: string; - const ABuildConfig: IOTABuildConfiguration): string; -begin - if Assigned(ABuildConfig) then - Result := ExpandConfiguration(ASource, ABuildConfig) - else - Result := ASource; - Result := ExpandVars(Result); -end; - -class function TPyEnvironmentOTAHelper.ExpandPath(const ABaseDir, - ARelativeDir: string): string; -var - LBuffer: array [0..MAX_PATH - 1] of Char; -begin - if PathIsRelative(PChar(ARelativeDir)) then - Result := IncludeTrailingPathDelimiter(ABaseDir) + ARelativeDir - else - Result := ARelativeDir; - if PathCanonicalize(@LBuffer[0], PChar(Result)) then - Result := LBuffer; -end; - -class function TPyEnvironmentOTAHelper.ExpandVars( - const ASource: string): string; -var - LVars: TStrings; - I: Integer; -begin - Result := ASource; - if not Result.IsEmpty then begin - LVars := TStringList.Create; - try - GetEnvironmentVars(LVars, True); - for I := 0 to LVars.Count - 1 do begin - Result := StringReplace(Result, '$(' + LVars.Names[I] + ')', LVars.Values[LVars.Names[I]], [rfReplaceAll, rfIgnoreCase]); - Result := StringReplace(Result, '%' + LVars.Names[I] + '%', LVars.Values[LVars.Names[I]], [rfReplaceAll, rfIgnoreCase]); - end; - finally - LVars.Free; - end; - end; -end; - -class function TPyEnvironmentOTAHelper.GetEnvironmentVar(const AName: string; - AExpand: Boolean): string; -const - BufSize = 1024; -var - Len: Integer; - Buffer: array[0..BufSize - 1] of Char; - LExpanded: string; -begin - Result := ''; - Len := Winapi.Windows.GetEnvironmentVariable(PChar(AName), @Buffer, BufSize); - if Len < BufSize then - SetString(Result, PChar(@Buffer), Len) - else begin - SetLength(Result, Len - 1); - Winapi.Windows.GetEnvironmentVariable(PChar(AName), PChar(Result), Len); - end; - if AExpand then begin - LExpanded := Result; - if ExpandEnvironmentVar(LExpanded) then - Result := LExpanded; - end; -end; - -class function TPyEnvironmentOTAHelper.GetEnvironmentVars(const AVars: TStrings; - AExpand: Boolean): Boolean; -var - LRaw: PChar; - LExpanded: string; - I: Integer; -begin - AVars.BeginUpdate; - try - AVars.Clear; - LRaw := GetEnvironmentStrings; - try - MultiSzToStrings(AVars, LRaw); - Result := True; - finally - FreeEnvironmentStrings(LRaw); - end; - if AExpand then begin - for I := 0 to AVars.Count - 1 do begin - LExpanded := AVars[I]; - if ExpandEnvironmentVar(LExpanded) then - AVars[I] := LExpanded; - end; - end; - finally - AVars.EndUpdate; - end; -end; - -class function TPyEnvironmentOTAHelper.GetProjectOptionsConfigurations( - const AProject: IOTAProject): IOTAProjectOptionsConfigurations; -var - LProjectOptions: IOTAProjectOptions; -begin - Result := nil; - if AProject <> nil then begin - LProjectOptions := AProject.ProjectOptions; - if LProjectOptions <> nil then - Supports(LProjectOptions, IOTAProjectOptionsConfigurations, Result); - end; -end; - -class function TPyEnvironmentOTAHelper.InsertOptionValue(const AValues, AValue, - ASeparator: string): string; -var - LValues: TArray; - I: Integer; -begin - LValues := AValues.Split([ASeparator], TStringSplitOptions.None); - try - for I := 0 to Length(LValues) - 1 do begin - if SameText(LValues[I], AValue) then begin - LValues[I] := AValue; - Exit; - end; - end; - LValues := LValues + [AValue]; - finally - if LValues = nil then - Result := '' - else - Result := string.Join(ASeparator, LValues); - end; -end; - -class procedure TPyEnvironmentOTAHelper.MultiSzToStrings(const ADest: TStrings; - const ASource: PChar); -var - P: PChar; -begin - ADest.BeginUpdate; - try - ADest.Clear; - if ASource <> nil then begin - P := ASource; - while P^ <> #0 do begin - ADest.Add(P); - P := StrEnd(P); - Inc(P); - end; - end; - finally - ADest.EndUpdate; - end; -end; - -class function TPyEnvironmentOTAHelper.RemoveOptionValue(const AValues, AValue, - ASeparator: string): string; -var - LValues: TArray; - LNewValues: TArray; - I: Integer; -begin - LNewValues := []; - LValues := AValues.Split([ASeparator], TStringSplitOptions.None); - for I := 0 to Length(LValues) - 1 do - if not SameText(LValues[I], AValue) then - LNewValues := LNewValues + [LValues[I]]; - if LNewValues = nil then - Result := '' - else - Result := string.Join(ASeparator, LNewValues); -end; - -class procedure TPyEnvironmentOTAHelper.StrResetLength(var S: string); -begin - SetLength(S, StrLen(PChar(S))); -end; - -class function TPyEnvironmentOTAHelper.TryGetProjectOutputPath( - const AProject: IOTAProject; ABuildConfig: IOTABuildConfiguration; - out AOutputPath: string): Boolean; -var - LOptions: IOTAProjectOptions; - LOptionsConfigurations: IOTAProjectOptionsConfigurations; - LRelativeOutputPath: string; -begin - Result := False; - try - if Assigned(AProject) then begin - AOutputPath := TPath.GetDirectoryName(AProject.FileName); - LOptions := AProject.ProjectOptions; - if LOptions <> nil then begin - if not Assigned(ABuildConfig) then begin - LOptionsConfigurations := GetProjectOptionsConfigurations(AProject); - if Assigned(LOptionsConfigurations) then - ABuildConfig := LOptionsConfigurations.ActiveConfiguration; - end; - - if Assigned(ABuildConfig) then begin - LRelativeOutputPath := LOptions.Values[OutputDirPropertyName]; - AOutputPath := ExpandOutputPath(ExpandPath(AOutputPath, LRelativeOutputPath), ABuildConfig); - Result := True; - end else - Result := False; - end else - Result := True; - end; - finally - if not Result then - AOutputPath := ''; - end; -end; - -class function TPyEnvironmentOTAHelper.TryCopyFileToOutputPath( - const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; - const AConfig: TPyEnvironmentProjectConfig; const AFileName: string): Boolean; -var - LProjectOutputPath: string; -begin - Result := False; - if (APlatform <> TPyEnvironmentProjectPlatform.Unknown) and TFile.Exists(AFileName) - and TryGetProjectOutputPath(AProject, APlatform, AConfig, LProjectOutputPath) then - begin - try - if not TDirectory.Exists(LProjectOutputPath) then - TDirectory.CreateDirectory(LProjectOutputPath); - TFile.Copy(AFileName, TPath.Combine(LProjectOutputPath, TPath.GetFileName(AFileName)), True); - Result := True; - except - Result := False; - end; - end; -end; - -class function TPyEnvironmentOTAHelper.TryCopyFileToOutputPathOfActiveBuild( - const AProject: IOTAProject; const AFileName: string): Boolean; -var - LPlatform: TPyEnvironmentProjectPlatform; - LConfig: TPyEnvironmentProjectConfig; -begin - LPlatform := TPyEnvironmentProjectPlatform.Unknown; - LConfig := TPyEnvironmentProjectConfig.Release; - if Assigned(AProject) then begin - LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform); - LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); - end; - Result := TryCopyFileToOutputPath(AProject, LPlatform, LConfig, AFileName); -end; - -class function TPyEnvironmentOTAHelper.TryGetBuildConfig( - const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; - const AConfig: TPyEnvironmentProjectConfig; - out ABuildConfig: IOTABuildConfiguration): Boolean; -var - LOptionsConfigurations: IOTAProjectOptionsConfigurations; - LConfigName: string; - I: Integer; -begin - Result := False; - ABuildConfig := nil; - if APlatform <> TPyEnvironmentProjectPlatform.Unknown then begin - LOptionsConfigurations := GetProjectOptionsConfigurations(AProject); - if Assigned(LOptionsConfigurations) then begin - LConfigName := AConfig.ToString; - for I := LOptionsConfigurations.ConfigurationCount - 1 downto 0 do begin - ABuildConfig := LOptionsConfigurations.Configurations[I]; - if ContainsOptionValue(ABuildConfig.Value[sDefine], LConfigName) then begin - ABuildConfig := ABuildConfig.PlatformConfiguration[APlatform.ToString]; - Exit(Assigned(ABuildConfig)); - end; - end; - end; - end; -end; - -class function TPyEnvironmentOTAHelper.TryGetProjectOutputPath( - const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; - const AConfig: TPyEnvironmentProjectConfig; out AOutputPath: string): Boolean; -var - LBuildConfig: IOTABuildConfiguration; -begin - Result := (APlatform <> TPyEnvironmentProjectPlatform.Unknown) and - TryGetBuildConfig(AProject, APlatform, AConfig, LBuildConfig) and - TryGetProjectOutputPath(AProject, LBuildConfig, AOutputPath) and - TPath.HasValidPathChars(AOutputPath, False); - if not Result then - AOutputPath := ''; -end; - -class function TPyEnvironmentOTAHelper.TryGetProjectOutputPathOfActiveBuild( - const AProject: IOTAProject; out AOutputPath: string): Boolean; -var - LPlatform: TPyEnvironmentProjectPlatform; - LConfig: TPyEnvironmentProjectConfig; -begin - LPlatform := TPyEnvironmentProjectPlatform.Unknown; - LConfig := TPyEnvironmentProjectConfig.Release; - if Assigned(AProject) then begin - LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform); - LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); - end; - Result := TryGetProjectOutputPath(AProject, LPlatform, LConfig, AOutputPath); -end; - -class function TPyEnvironmentOTAHelper.TryRemoveOutputFile( - const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; - const AConfig: TPyEnvironmentProjectConfig; AFileName: string): Boolean; -var - LProjectOutputPath: string; -begin - Result := False; - if (APlatform <> TPyEnvironmentProjectPlatform.Unknown) and TPyEnvironmentOTAHelper.TryGetProjectOutputPathOfActiveBuild(AProject, LProjectOutputPath) then begin - AFileName := TPath.Combine(LProjectOutputPath, AFileName); - if TFile.Exists(AFileName) then begin - try - TFile.Delete(AFileName); - Result := True; - except - Result := False; - end; - end; - end; -end; - -class function TPyEnvironmentOTAHelper.TryRemoveOutputFileOfActiveBuild( - const AProject: IOTAProject; const AFileName: string): Boolean; -var - LPlatform: TPyEnvironmentProjectPlatform; - LConfig: TPyEnvironmentProjectConfig; -begin - LPlatform := TPyEnvironmentProjectPlatform.Unknown; - LConfig := TPyEnvironmentProjectConfig.Release; - if Assigned(AProject) then begin - LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform); - LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); - end; - Result := TryRemoveOutputFile(AProject, LPlatform, LConfig, AFileName); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Helper' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Helpers for the Python project menu *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Helper; + +interface + +uses + System.Classes, + ToolsAPI, DeploymentAPI, + PyEnvironment.Project.IDE.Types; + +type + TPyEnvironmentProjectHelper = record + strict private + const + PYTHON_VERSION_PREFIX = 'PYTHONVER'; + class function ContainsStringInArray(const AString: string; const AArray: TArray): Boolean; static; inline; + class function GetIsPyEnvironmentDefined( + const AProject: IOTAProject): Boolean; static; + class procedure SetIsPyEnvironmentDefined(const AProject: IOTAProject; + const AValue: Boolean); static; + class function GetCurrentPythonVersion( + const AProject: IOTAProject): string; static; + class procedure SetCurrentPythonVersion(const AProject: IOTAProject; + const AValue: string); static; + class function BuildPythonVersionConditional(const APythonVersion: string): string; static; + public + class procedure AddDeployFile(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const ADeployFile: TPyEnvironmentDeployFile); static; + class procedure RemoveDeployFile(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const APlatform: TPyEnvironmentProjectPlatform; ALocalFileName: string; const ARemoteDir: string; const AUpdateLocalFileName: boolean = true); static; + class procedure RemoveDeployFilesOfClass(const AProject: IOTAProject); overload; static; + class procedure RemoveDeployFilesOfClass(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const APlatform: TPyEnvironmentProjectPlatform); overload; static; + class procedure RemoveUnexpectedDeployFilesOfClass(const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; const APlatform: TPyEnvironmentProjectPlatform; const AAllowedFiles: TArray); static; + + class function IsPyEnvironmentDefinedForPlatform(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig): Boolean; static; + class function SupportsEnvironmentDeployment(const AProject: IOTAProject): Boolean; static; + + class property IsPyEnvironmentDefined[const AProject: IOTAProject]: Boolean read GetIsPyEnvironmentDefined write SetIsPyEnvironmentDefined; + class property CurrentPythonVersion[const AProject: IOTAProject]: string read GetCurrentPythonVersion write SetCurrentPythonVersion; + end; + + TPyEnvironmentOTAHelper = record + strict private + const + DefaultOptionsSeparator = ';'; + OutputDirPropertyName = 'OutputDir'; + class function ExpandConfiguration(const ASource: string; const AConfig: IOTABuildConfiguration): string; static; + class function ExpandEnvironmentVar(var AValue: string): Boolean; static; + class function ExpandOutputPath(const ASource: string; const ABuildConfig: IOTABuildConfiguration): string; static; + class function ExpandPath(const ABaseDir, ARelativeDir: string): string; static; + class function ExpandVars(const ASource: string): string; static; + class function GetEnvironmentVars(const AVars: TStrings; AExpand: Boolean): Boolean; static; + class function GetProjectOptionsConfigurations(const AProject: IOTAProject): IOTAProjectOptionsConfigurations; static; + class procedure MultiSzToStrings(const ADest: TStrings; const ASource: PChar); static; + class procedure StrResetLength(var S: string); static; + class function TryGetProjectOutputPath(const AProject: IOTAProject; ABuildConfig: IOTABuildConfiguration; out AOutputPath: string): Boolean; overload; static; + public + class function ContainsOptionValue(const AValues, AValue: string; const ASeparator: string = DefaultOptionsSeparator): Boolean; static; + class function InsertOptionValue(const AValues, AValue: string; const ASeparator: string = DefaultOptionsSeparator): string; static; + class function RemoveOptionValue(const AValues, AValue: string; const ASeparator: string = DefaultOptionsSeparator): string; static; + + class function GetEnvironmentVar(const AName: string; AExpand: Boolean): string; static; + class function TryCopyFileToOutputPath(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; const AFileName: string): Boolean; static; + class function TryCopyFileToOutputPathOfActiveBuild(const AProject: IOTAProject; const AFileName: string): Boolean; static; + class function TryGetBuildConfig(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; out ABuildConfig: IOTABuildConfiguration): Boolean; static; + class function TryGetProjectOutputPath(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; out AOutputPath: string): Boolean; overload; static; + class function TryGetProjectOutputPathOfActiveBuild(const AProject: IOTAProject; out AOutputPath: string): Boolean; static; + class function TryRemoveOutputFile(const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; const AConfig: TPyEnvironmentProjectConfig; AFileName: string): Boolean; static; + class function TryRemoveOutputFileOfActiveBuild(const AProject: IOTAProject; const AFileName: string): Boolean; static; + end; + +implementation + +uses + System.SysUtils, System.IOUtils, System.Generics.Collections, + DccStrs, + Winapi.Windows, Winapi.ShLwApi, + PyEnvironment.Project.IDE.Deploy; + +{ TPyEnvironmentProjectHelper } + +class procedure TPyEnvironmentProjectHelper.AddDeployFile( + const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; + const ADeployFile: TPyEnvironmentDeployFile); +type + TDeployFileExistence = (DoesNotExist, AlreadyExists, NeedReplaced); + + function GetDeployFileExistence(const AProjectDeployment: IProjectDeployment; + const ALocalFileName, ARemoteDir, APlatformName, AConfigName: string): TDeployFileExistence; + var + LRemoteFileName: string; + LFile: IProjectDeploymentFile; + LFiles: TDictionary.TValueCollection; + begin + Result := TDeployFileExistence.DoesNotExist; + LRemoteFileName := TPath.Combine(ARemoteDir, TPath.GetFileName(ALocalFileName)); + LFiles := AProjectDeployment.Files; + if Assigned(LFiles) then begin + for LFile in LFiles do begin + if (LFile.FilePlatform = APlatformName) and (LFile.Configuration = AConfigName) then begin + if SameText(LRemoteFileName, TPath.Combine(LFile.RemoteDir[APlatformName], LFile.RemoteName[APlatformName])) then begin + if (LFile.LocalName = ALocalFileName) and (LFile.DeploymentClass = TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) and + (LFile.Condition = ADeployFile.Condition) and (LFile.Operation[APlatformName] = ADeployFile.Operation) and + LFile.Enabled[APlatformName] and LFile.Overwrite[APlatformName] and + (LFile.Required = ADeployFile.Required) and (Result = TDeployFileExistence.DoesNotExist) then + begin + Result := TDeployFileExistence.AlreadyExists; + end else + Exit(TDeployFileExistence.NeedReplaced); + end; + end; + end; + end; + end; + + function CanDeployFile(const ADeployFileExistence: TDeployFileExistence): boolean; + begin + Result := (ADeployFileExistence in [TDeployFileExistence.NeedReplaced, + TDeployFileExistence.DoesNotExist]) + and (AConfig in ADeployFile.Configs) + end; + + procedure DoAddDeployFile(const AProjectDeployment: IProjectDeployment; + const ALocalFileName, APlatformName, AConfigName: string); + var + LFile: IProjectDeploymentFile; + begin + LFile := AProjectDeployment.CreateFile(AConfigName, APlatformName, ALocalFileName); + if Assigned(LFile) then begin + LFile.Overwrite[APlatformName] := True; + LFile.Enabled[APlatformName] := True; + LFile.Required := ADeployFile.Required; + LFile.Condition := ADeployFile.Condition; + LFile.Operation[APlatformName] := ADeployFile.Operation; + LFile.RemoteDir[APlatformName] := ADeployFile.RemotePath; + LFile.DeploymentClass := TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS; + LFile.RemoteName[APlatformName] := TPath.GetFileName(ALocalFileName); + AProjectDeployment.AddFile(AConfigName, APlatformName, LFile); + end; + end; + +var + LProjectDeployment: IProjectDeployment; + LConfigName: string; + LPlatformName: string; + LLocalFileName: string; + LDeployFileExistence: TDeployFileExistence; +begin + if (ADeployFile.LocalFileName <> '') and Supports(AProject, IProjectDeployment, LProjectDeployment) then begin + LConfigName := AConfig.ToString; + LPlatformName := ADeployFile.Platform.ToString; + + if ADeployFile.UpdateLocalFileName then + LLocalFileName := TPath.Combine(TPyEnvironmentProjectDeploy.Path, ADeployFile.LocalFileName) + else + LLocalFilename := ADeployFile.LocalFileName; + + LDeployFileExistence := GetDeployFileExistence(LProjectDeployment, LLocalFileName, ADeployFile.RemotePath, LPlatformName, LConfigName); + if LDeployFileExistence = TDeployFileExistence.NeedReplaced then + RemoveDeployFile(AProject, AConfig, ADeployFile.Platform, ADeployFile.LocalFileName, ADeployFile.RemotePath, ADeployFile.UpdateLocalFileName); + if CanDeployFile(LDeployFileExistence) then + DoAddDeployFile(LProjectDeployment, LLocalFileName, LPlatformName, LConfigName); + end; +end; + +class function TPyEnvironmentProjectHelper.BuildPythonVersionConditional( + const APythonVersion: string): string; +begin + Result := PYTHON_VERSION_PREFIX + APythonVersion.Replace('.', '', [rfReplaceAll]); +end; + +class function TPyEnvironmentProjectHelper.ContainsStringInArray( + const AString: string; const AArray: TArray): Boolean; +var + I: Integer; +begin + Result := False; + for I := Low(AArray) to High(AArray) do + if AArray[I] = AString then + Exit(True); +end; + +class function TPyEnvironmentProjectHelper.GetCurrentPythonVersion( + const AProject: IOTAProject): string; +var + LBaseConfiguration: IOTABuildConfiguration; + LOptionsConfigurations: IOTAProjectOptionsConfigurations; + LPythonVersion: string; +begin + if not (Assigned(AProject) + and Supports(AProject.ProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations) + and Assigned(LOptionsConfigurations.BaseConfiguration)) then + Exit(String.Empty); + + LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; + if Assigned(LBaseConfiguration) then begin + for LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do begin + if TPyEnvironmentOTAHelper.ContainsOptionValue( + LBaseConfiguration.Value[sDefine], + BuildPythonVersionConditional(LPythonVersion)) then + Exit(LPythonVersion); + end; + end; + + Result := String.Empty; +end; + +class function TPyEnvironmentProjectHelper.GetIsPyEnvironmentDefined( + const AProject: IOTAProject): Boolean; +var + LBaseConfiguration: IOTABuildConfiguration; + LOptionsConfigurations: IOTAProjectOptionsConfigurations; +begin + Result := Assigned(AProject) + and Supports(AProject.ProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations); + + if Result then begin + LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; + Result := Assigned(LBaseConfiguration) + and TPyEnvironmentOTAHelper.ContainsOptionValue( + LBaseConfiguration.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_USE_PYTHON); + end; +end; + +class function TPyEnvironmentProjectHelper.IsPyEnvironmentDefinedForPlatform( + const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; + const AConfig: TPyEnvironmentProjectConfig): Boolean; +var + LBuildConfig: IOTABuildConfiguration; +begin + Assert(IsPyEnvironmentDefined[AProject]); + Result := TPyEnvironmentOTAHelper.TryGetBuildConfig( + AProject, APlatform, AConfig, LBuildConfig) + and not TPyEnvironmentOTAHelper.ContainsOptionValue( + LBuildConfig.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_NO_USE_PYTHON); +end; + +class procedure TPyEnvironmentProjectHelper.RemoveDeployFile( + const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; + const APlatform: TPyEnvironmentProjectPlatform; ALocalFileName: string; + const ARemoteDir: string; const AUpdateLocalFileName: boolean); +var + LProjectDeployment: IProjectDeployment; + LFiles: TDictionary.TValueCollection; + LFile: IProjectDeploymentFile; + LRemoteFileName: string; + LRemoveFiles: TArray; +begin + if (ALocalFileName <> '') and Supports(AProject, IProjectDeployment, LProjectDeployment) then begin + if AUpdateLocalFileName then + ALocalFileName := TPath.Combine(TPyEnvironmentProjectDeploy.Path, ALocalFileName) + else + ALocalFileName := ALocalFileName; + + LProjectDeployment.RemoveFile(AConfig.ToString, APlatform.ToString, ALocalFileName); + LFiles := LProjectDeployment.Files; + if Assigned(LFiles) then begin + LRemoteFileName := TPath.Combine(ARemoteDir, TPath.GetFileName(ALocalFileName)); + LRemoveFiles := []; + for LFile in LFiles do + if SameText(LRemoteFileName, TPath.Combine(LFile.RemoteDir[APlatform.ToString], LFile.RemoteName[APlatform.ToString])) then + LRemoveFiles := LRemoveFiles + [LFile]; + for LFile in LRemoveFiles do + LProjectDeployment.RemoveFile(AConfig.ToString, APlatform.ToString, LFile.LocalName); + end; + end; +end; + +class procedure TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass( + const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; + const APlatform: TPyEnvironmentProjectPlatform); +var + LProjectDeployment: IProjectDeployment; + LFile: IProjectDeploymentFile; + LConfigName: string; + LPlatformName: string; +begin + if Supports(AProject, IProjectDeployment, LProjectDeployment) then begin + LConfigName := AConfig.ToString; + LPlatformName := APlatform.ToString; + for LFile in LProjectDeployment.GetFilesOfClass(TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) do + if (LFile.Configuration = LConfigName) and ContainsStringInArray(LPlatformName, LFile.Platforms) then + LProjectDeployment.RemoveFile(LConfigName, LPlatformName, LFile.LocalName); + end; +end; + +class procedure TPyEnvironmentProjectHelper.RemoveUnexpectedDeployFilesOfClass( + const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; + const APlatform: TPyEnvironmentProjectPlatform; + const AAllowedFiles: TArray); + + function IsAllowedFile(const AFile: IProjectDeploymentFile; const APlatformName: string): Boolean; + var + LDeployFile: TPyEnvironmentDeployFile; + begin + Result := False; + for LDeployFile in AAllowedFiles do begin + if (AFile.LocalName = LDeployFile.LocalFileName) and SameText(AFile.RemoteDir[APlatformName], LDeployFile.RemotePath) and + SameText(AFile.RemoteName[APlatformName], TPath.GetFileName(LDeployFile.LocalFileName)) and + (AFile.DeploymentClass = TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) and + (AFile.Condition = LDeployFile.Condition) and (AFile.Operation[APlatformName] = LDeployFile.Operation) and + AFile.Enabled[APlatformName] and AFile.Overwrite[APlatformName] and + (AFile.Required = LDeployFile.Required) then + begin + Exit(True); + end; + end; + end; + +var + LProjectDeployment: IProjectDeployment; + LFile: IProjectDeploymentFile; + LConfigName: string; + LPlatformName: string; +begin + if Supports(AProject, IProjectDeployment, LProjectDeployment) then begin + LConfigName := AConfig.ToString; + LPlatformName := APlatform.ToString; + for LFile in LProjectDeployment.GetFilesOfClass(TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS) do begin + if (LFile.Configuration = LConfigName) and ContainsStringInArray(LPlatformName, LFile.Platforms) and + not IsAllowedFile(LFile, LPlatformName) then + begin + LProjectDeployment.RemoveFile(LConfigName, LPlatformName, LFile.LocalName); + end; + end; + end; +end; + +class procedure TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass( + const AProject: IOTAProject); +var + LProjectDeployment: IProjectDeployment; +begin + if Supports(AProject, IProjectDeployment, LProjectDeployment) then + LProjectDeployment.RemoveFilesOfClass(TPyEnvironmentProjectDeploy.DEPLOYMENT_CLASS); +end; + +class procedure TPyEnvironmentProjectHelper.SetCurrentPythonVersion( + const AProject: IOTAProject; const AValue: string); +var + LProjectOptions: IOTAProjectOptions; + LOptionsConfigurations: IOTAProjectOptionsConfigurations; + LBaseConfiguration: IOTABuildConfiguration; + LPythonVersion: string; +begin + if Assigned(AProject) then begin + LProjectOptions := AProject.ProjectOptions; + if Assigned(LProjectOptions) then begin + if Supports(LProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations) then begin + LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; + if Assigned(LBaseConfiguration) then begin + LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; + for LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do + LBaseConfiguration.Value[sDefine] := + TPyEnvironmentOTAHelper.RemoveOptionValue( + LBaseConfiguration.Value[sDefine], + BuildPythonVersionConditional(LPythonVersion)); + + if not AValue.IsEmpty() then + LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper + .InsertOptionValue(LBaseConfiguration.Value[sDefine], + BuildPythonVersionConditional(AValue)); + end; + end; + LProjectOptions.ModifiedState := True; + end; + end; +end; + +class procedure TPyEnvironmentProjectHelper.SetIsPyEnvironmentDefined( + const AProject: IOTAProject; const AValue: Boolean); +var + LProjectOptions: IOTAProjectOptions; + LOptionsConfigurations: IOTAProjectOptionsConfigurations; + LBaseConfiguration: IOTABuildConfiguration; +begin + if Assigned(AProject) then begin + LProjectOptions := AProject.ProjectOptions; + if Assigned(LProjectOptions) then begin + if Supports(LProjectOptions, IOTAProjectOptionsConfigurations, LOptionsConfigurations) then begin + LBaseConfiguration := LOptionsConfigurations.BaseConfiguration; + if Assigned(LBaseConfiguration) then begin + if AValue then + LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper + .InsertOptionValue( + LBaseConfiguration.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_USE_PYTHON) + else + LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper + .RemoveOptionValue( + LBaseConfiguration.Value[sDefine], TPyEnvironmentProjectDeploy.PROJECT_USE_PYTHON); + end; + end; + LProjectOptions.ModifiedState := True; + end; + end; +end; + +class function TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment( + const AProject: IOTAProject): Boolean; +begin + Result := Assigned(AProject) + and AProject.FileName.EndsWith('.dproj', True) + and ((AProject.ApplicationType = sApplication) + or + (AProject.ApplicationType = sConsole)); +end; + +{ TPyEnvironmentOTAHelper } + +class function TPyEnvironmentOTAHelper.ContainsOptionValue(const AValues, + AValue, ASeparator: string): Boolean; +var + LValues: TArray; + I: Integer; +begin + LValues := AValues.Split([ASeparator], TStringSplitOptions.None); + for I := 0 to Length(LValues) - 1 do + if SameText(LValues[I], AValue) then + Exit(True); + Result := False; +end; + +class function TPyEnvironmentOTAHelper.ExpandConfiguration( + const ASource: string; const AConfig: IOTABuildConfiguration): string; +begin + Result := StringReplace(ASource, '$(Platform)', AConfig.Platform, [rfReplaceAll, rfIgnoreCase]); + Result := StringReplace(Result, '$(Config)', AConfig.Name, [rfReplaceAll, rfIgnoreCase]); +end; + +class function TPyEnvironmentOTAHelper.ExpandEnvironmentVar( + var AValue: string): Boolean; +var + R: Integer; + LExpanded: string; +begin + SetLength(LExpanded, 1); + R := ExpandEnvironmentStrings(PChar(AValue), PChar(LExpanded), 0); + SetLength(LExpanded, R); + Result := ExpandEnvironmentStrings(PChar(AValue), PChar(LExpanded), R) <> 0; + if Result then begin + StrResetLength(LExpanded); + AValue := LExpanded; + end; +end; + +class function TPyEnvironmentOTAHelper.ExpandOutputPath(const ASource: string; + const ABuildConfig: IOTABuildConfiguration): string; +begin + if Assigned(ABuildConfig) then + Result := ExpandConfiguration(ASource, ABuildConfig) + else + Result := ASource; + Result := ExpandVars(Result); +end; + +class function TPyEnvironmentOTAHelper.ExpandPath(const ABaseDir, + ARelativeDir: string): string; +var + LBuffer: array [0..MAX_PATH - 1] of Char; +begin + if PathIsRelative(PChar(ARelativeDir)) then + Result := IncludeTrailingPathDelimiter(ABaseDir) + ARelativeDir + else + Result := ARelativeDir; + if PathCanonicalize(@LBuffer[0], PChar(Result)) then + Result := LBuffer; +end; + +class function TPyEnvironmentOTAHelper.ExpandVars( + const ASource: string): string; +var + LVars: TStrings; + I: Integer; +begin + Result := ASource; + if not Result.IsEmpty then begin + LVars := TStringList.Create; + try + GetEnvironmentVars(LVars, True); + for I := 0 to LVars.Count - 1 do begin + Result := StringReplace(Result, '$(' + LVars.Names[I] + ')', LVars.Values[LVars.Names[I]], [rfReplaceAll, rfIgnoreCase]); + Result := StringReplace(Result, '%' + LVars.Names[I] + '%', LVars.Values[LVars.Names[I]], [rfReplaceAll, rfIgnoreCase]); + end; + finally + LVars.Free; + end; + end; +end; + +class function TPyEnvironmentOTAHelper.GetEnvironmentVar(const AName: string; + AExpand: Boolean): string; +const + BufSize = 1024; +var + Len: Integer; + Buffer: array[0..BufSize - 1] of Char; + LExpanded: string; +begin + Result := ''; + Len := Winapi.Windows.GetEnvironmentVariable(PChar(AName), @Buffer, BufSize); + if Len < BufSize then + SetString(Result, PChar(@Buffer), Len) + else begin + SetLength(Result, Len - 1); + Winapi.Windows.GetEnvironmentVariable(PChar(AName), PChar(Result), Len); + end; + if AExpand then begin + LExpanded := Result; + if ExpandEnvironmentVar(LExpanded) then + Result := LExpanded; + end; +end; + +class function TPyEnvironmentOTAHelper.GetEnvironmentVars(const AVars: TStrings; + AExpand: Boolean): Boolean; +var + LRaw: PChar; + LExpanded: string; + I: Integer; +begin + AVars.BeginUpdate; + try + AVars.Clear; + LRaw := GetEnvironmentStrings; + try + MultiSzToStrings(AVars, LRaw); + Result := True; + finally + FreeEnvironmentStrings(LRaw); + end; + if AExpand then begin + for I := 0 to AVars.Count - 1 do begin + LExpanded := AVars[I]; + if ExpandEnvironmentVar(LExpanded) then + AVars[I] := LExpanded; + end; + end; + finally + AVars.EndUpdate; + end; +end; + +class function TPyEnvironmentOTAHelper.GetProjectOptionsConfigurations( + const AProject: IOTAProject): IOTAProjectOptionsConfigurations; +var + LProjectOptions: IOTAProjectOptions; +begin + Result := nil; + if AProject <> nil then begin + LProjectOptions := AProject.ProjectOptions; + if LProjectOptions <> nil then + Supports(LProjectOptions, IOTAProjectOptionsConfigurations, Result); + end; +end; + +class function TPyEnvironmentOTAHelper.InsertOptionValue(const AValues, AValue, + ASeparator: string): string; +var + LValues: TArray; + I: Integer; +begin + LValues := AValues.Split([ASeparator], TStringSplitOptions.None); + try + for I := 0 to Length(LValues) - 1 do begin + if SameText(LValues[I], AValue) then begin + LValues[I] := AValue; + Exit; + end; + end; + LValues := LValues + [AValue]; + finally + if LValues = nil then + Result := '' + else + Result := string.Join(ASeparator, LValues); + end; +end; + +class procedure TPyEnvironmentOTAHelper.MultiSzToStrings(const ADest: TStrings; + const ASource: PChar); +var + P: PChar; +begin + ADest.BeginUpdate; + try + ADest.Clear; + if ASource <> nil then begin + P := ASource; + while P^ <> #0 do begin + ADest.Add(P); + P := StrEnd(P); + Inc(P); + end; + end; + finally + ADest.EndUpdate; + end; +end; + +class function TPyEnvironmentOTAHelper.RemoveOptionValue(const AValues, AValue, + ASeparator: string): string; +var + LValues: TArray; + LNewValues: TArray; + I: Integer; +begin + LNewValues := []; + LValues := AValues.Split([ASeparator], TStringSplitOptions.None); + for I := 0 to Length(LValues) - 1 do + if not SameText(LValues[I], AValue) then + LNewValues := LNewValues + [LValues[I]]; + if LNewValues = nil then + Result := '' + else + Result := string.Join(ASeparator, LNewValues); +end; + +class procedure TPyEnvironmentOTAHelper.StrResetLength(var S: string); +begin + SetLength(S, StrLen(PChar(S))); +end; + +class function TPyEnvironmentOTAHelper.TryGetProjectOutputPath( + const AProject: IOTAProject; ABuildConfig: IOTABuildConfiguration; + out AOutputPath: string): Boolean; +var + LOptions: IOTAProjectOptions; + LOptionsConfigurations: IOTAProjectOptionsConfigurations; + LRelativeOutputPath: string; +begin + Result := False; + try + if Assigned(AProject) then begin + AOutputPath := TPath.GetDirectoryName(AProject.FileName); + LOptions := AProject.ProjectOptions; + if LOptions <> nil then begin + if not Assigned(ABuildConfig) then begin + LOptionsConfigurations := GetProjectOptionsConfigurations(AProject); + if Assigned(LOptionsConfigurations) then + ABuildConfig := LOptionsConfigurations.ActiveConfiguration; + end; + + if Assigned(ABuildConfig) then begin + LRelativeOutputPath := LOptions.Values[OutputDirPropertyName]; + AOutputPath := ExpandOutputPath(ExpandPath(AOutputPath, LRelativeOutputPath), ABuildConfig); + Result := True; + end else + Result := False; + end else + Result := True; + end; + finally + if not Result then + AOutputPath := ''; + end; +end; + +class function TPyEnvironmentOTAHelper.TryCopyFileToOutputPath( + const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; + const AConfig: TPyEnvironmentProjectConfig; const AFileName: string): Boolean; +var + LProjectOutputPath: string; +begin + Result := False; + if (APlatform <> TPyEnvironmentProjectPlatform.Unknown) and TFile.Exists(AFileName) + and TryGetProjectOutputPath(AProject, APlatform, AConfig, LProjectOutputPath) then + begin + try + if not TDirectory.Exists(LProjectOutputPath) then + TDirectory.CreateDirectory(LProjectOutputPath); + TFile.Copy(AFileName, TPath.Combine(LProjectOutputPath, TPath.GetFileName(AFileName)), True); + Result := True; + except + Result := False; + end; + end; +end; + +class function TPyEnvironmentOTAHelper.TryCopyFileToOutputPathOfActiveBuild( + const AProject: IOTAProject; const AFileName: string): Boolean; +var + LPlatform: TPyEnvironmentProjectPlatform; + LConfig: TPyEnvironmentProjectConfig; +begin + LPlatform := TPyEnvironmentProjectPlatform.Unknown; + LConfig := TPyEnvironmentProjectConfig.Release; + if Assigned(AProject) then begin + LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform); + LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); + end; + Result := TryCopyFileToOutputPath(AProject, LPlatform, LConfig, AFileName); +end; + +class function TPyEnvironmentOTAHelper.TryGetBuildConfig( + const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; + const AConfig: TPyEnvironmentProjectConfig; + out ABuildConfig: IOTABuildConfiguration): Boolean; +var + LOptionsConfigurations: IOTAProjectOptionsConfigurations; + LConfigName: string; + I: Integer; +begin + Result := False; + ABuildConfig := nil; + if APlatform <> TPyEnvironmentProjectPlatform.Unknown then begin + LOptionsConfigurations := GetProjectOptionsConfigurations(AProject); + if Assigned(LOptionsConfigurations) then begin + LConfigName := AConfig.ToString; + for I := LOptionsConfigurations.ConfigurationCount - 1 downto 0 do begin + ABuildConfig := LOptionsConfigurations.Configurations[I]; + if ContainsOptionValue(ABuildConfig.Value[sDefine], LConfigName) then begin + ABuildConfig := ABuildConfig.PlatformConfiguration[APlatform.ToString]; + Exit(Assigned(ABuildConfig)); + end; + end; + end; + end; +end; + +class function TPyEnvironmentOTAHelper.TryGetProjectOutputPath( + const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; + const AConfig: TPyEnvironmentProjectConfig; out AOutputPath: string): Boolean; +var + LBuildConfig: IOTABuildConfiguration; +begin + Result := (APlatform <> TPyEnvironmentProjectPlatform.Unknown) and + TryGetBuildConfig(AProject, APlatform, AConfig, LBuildConfig) and + TryGetProjectOutputPath(AProject, LBuildConfig, AOutputPath) and + TPath.HasValidPathChars(AOutputPath, False); + if not Result then + AOutputPath := ''; +end; + +class function TPyEnvironmentOTAHelper.TryGetProjectOutputPathOfActiveBuild( + const AProject: IOTAProject; out AOutputPath: string): Boolean; +var + LPlatform: TPyEnvironmentProjectPlatform; + LConfig: TPyEnvironmentProjectConfig; +begin + LPlatform := TPyEnvironmentProjectPlatform.Unknown; + LConfig := TPyEnvironmentProjectConfig.Release; + if Assigned(AProject) then begin + LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform); + LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); + end; + Result := TryGetProjectOutputPath(AProject, LPlatform, LConfig, AOutputPath); +end; + +class function TPyEnvironmentOTAHelper.TryRemoveOutputFile( + const AProject: IOTAProject; const APlatform: TPyEnvironmentProjectPlatform; + const AConfig: TPyEnvironmentProjectConfig; AFileName: string): Boolean; +var + LProjectOutputPath: string; +begin + Result := False; + if (APlatform <> TPyEnvironmentProjectPlatform.Unknown) and TPyEnvironmentOTAHelper.TryGetProjectOutputPathOfActiveBuild(AProject, LProjectOutputPath) then begin + AFileName := TPath.Combine(LProjectOutputPath, AFileName); + if TFile.Exists(AFileName) then begin + try + TFile.Delete(AFileName); + Result := True; + except + Result := False; + end; + end; + end; +end; + +class function TPyEnvironmentOTAHelper.TryRemoveOutputFileOfActiveBuild( + const AProject: IOTAProject; const AFileName: string): Boolean; +var + LPlatform: TPyEnvironmentProjectPlatform; + LConfig: TPyEnvironmentProjectConfig; +begin + LPlatform := TPyEnvironmentProjectPlatform.Unknown; + LConfig := TPyEnvironmentProjectConfig.Release; + if Assigned(AProject) then begin + LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform); + LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); + end; + Result := TryRemoveOutputFile(AProject, LPlatform, LConfig, AFileName); +end; + +end. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas b/src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas index d8a4ec9..09dbbbd 100644 --- a/src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas +++ b/src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas @@ -1,464 +1,464 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Project.IDE.ManagerMenu' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Enable/Disable and select Python in the project menu *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Project.IDE.ManagerMenu; - -interface - -uses - System.Classes, - System.SysUtils, - ToolsAPI, - DesignIntf, - PyEnvironment.Project.IDE.Deploy, - PyEnvironment.Project.IDE.Types; - -type - TPyEnvironmentProjectManagerMenu = class(TNotifierObject, IOTALocalMenu, IOTAProjectManagerMenu) - strict private - FCaption: string; - FExecuteProc: TProc; - FName: string; - FParent: string; - FPosition: Integer; - FVerb: string; - FChecked: boolean; - strict protected - { IOTALocalMenu } - function GetCaption: string; - function GetChecked: Boolean; virtual; - function GetEnabled: Boolean; virtual; - function GetHelpContext: Integer; - function GetName: string; - function GetParent: string; - function GetPosition: Integer; - function GetVerb: string; - procedure SetCaption(const AValue: string); - procedure SetChecked(AValue: Boolean); - procedure SetEnabled(AValue: Boolean); - procedure SetHelpContext(AValue: Integer); - procedure SetName(const AValue: string); - procedure SetParent(const AValue: string); - procedure SetPosition(AValue: Integer); - procedure SetVerb(const AValue: string); - { IOTAProjectManagerMenu } - function GetIsMultiSelectable: Boolean; - procedure Execute(const AMenuContextList: IInterfaceList); overload; - function PostExecute(const AMenuContextList: IInterfaceList): Boolean; - function PreExecute(const AMenuContextList: IInterfaceList): Boolean; - procedure SetIsMultiSelectable(AValue: Boolean); - public - constructor Create(const ACaption, AVerb: string; const APosition: Integer; - const AExecuteProc: TProc = nil; const AName: string = ''; - const AParent: string = ''; const AChecked: boolean = false); - end; - - TPyEnvironmentProjectManagerMenuSeparator = class(TPyEnvironmentProjectManagerMenu) - public - constructor Create(const APosition: Integer); reintroduce; - end; - - TPyEnvironmentProjectManagerMenuEnablePythonEnvironment = class(TPyEnvironmentProjectManagerMenu) - strict private - FIsPyEnvironmentEnabled: boolean; - strict protected - function GetEnabled: Boolean; override; - public const - MENU_CAPTIONS: array[Boolean] of string = ('Enable Python', 'Disable Python'); - public - constructor Create(const AProject: IOTAProject; const APosition: Integer); reintroduce; - end; - - TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion = class(TPyEnvironmentProjectManagerMenu) - strict private const - MENU_VERB = 'PythonEnvironmentVersion'; - MENU_CAPTION = 'Python Version'; - strict private - FIsPyEnvironmentEnabled: boolean; - strict protected - function GetEnabled: Boolean; override; - procedure AddSubItems(const AProject: IOTAProject; const AList: IInterfaceList); - public - constructor Create(const ASubMenuList: IInterfaceList; const AProject: IOTAProject; const APosition: Integer); reintroduce; - end; - - TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem = class(TPyEnvironmentProjectManagerMenu) - strict private - FProject: IOTAProject; - FParent: IOTALocalMenu; - FPythonVersion: string; - procedure SetDeployFiles(const AProject: IOTAProject; - const AConfig: TPyEnvironmentProjectConfig; - const APlatform: TPyEnvironmentProjectPlatform; - const AEnabled: Boolean); - procedure SetPythonVersion(const AProject: IOTAProject; - const AEnabled: Boolean); - procedure SetDesginTimeCompsPythonVersion(const AProject: IOTAProject; - const APythonVersion: string); - strict protected - function GetEnabled: Boolean; override; - function GetChecked: boolean; override; - public - constructor Create(const AProject: IOTAProject; const APosition: Integer; - const AParent: IOTALocalMenu; - const APythonVersion: string); reintroduce; - end; - -implementation - -uses - System.IOUtils, - PyEnvironment.Project.IDE.Helper; - -function GetActiveFormEditor: IOTAFormEditor; -var - LIOTAModule: IOTAModule; - LIOTAEditor: IOTAEditor; - I: Integer; -begin - LIOTAModule := (BorlandIDEServices as IOTAModuleServices).CurrentModule(); - if Assigned(LIOTAModule) then begin - for I := 0 to LIOTAModule.GetModuleFileCount - 1 do begin - LIOTAEditor := LIOTAModule.GetModuleFileEditor(I); - if Supports(LIOTAEditor, IOTAFormEditor, Result) then - Exit; - end; - end; - Result := nil; -end; - -function GetActiveFormDesigner: IDesigner; -var - LIOTAFormEditor: IOTAFormEditor; -begin - LIOTAFormEditor := GetActiveFormEditor(); - if Assigned(LIOTAFormEditor) then - Result := (LIOTAFormEditor as INTAFormEditor).FormDesigner - else - Result := nil; -end; - -{ TPyEnvironmentProjectManagerMenu } - -constructor TPyEnvironmentProjectManagerMenu.Create(const ACaption, AVerb: string; - const APosition: Integer; const AExecuteProc: TProc = nil; - const AName: string = ''; const AParent: string = ''; const AChecked: boolean = false); -begin - inherited Create; - FCaption := ACaption; - FName := AName; - FParent := AParent; - FPosition := APosition; - FVerb := AVerb; - FExecuteProc := AExecuteProc; - FChecked := AChecked; -end; - -procedure TPyEnvironmentProjectManagerMenu.Execute(const AMenuContextList: IInterfaceList); -begin - if Assigned(FExecuteProc) then - FExecuteProc; -end; - -function TPyEnvironmentProjectManagerMenu.GetCaption: string; -begin - Result := FCaption; -end; - -function TPyEnvironmentProjectManagerMenu.GetChecked: Boolean; -begin - Result := FChecked; -end; - -function TPyEnvironmentProjectManagerMenu.GetEnabled: Boolean; -begin - Result := True; -end; - -function TPyEnvironmentProjectManagerMenu.GetHelpContext: Integer; -begin - Result := 0; -end; - -function TPyEnvironmentProjectManagerMenu.GetIsMultiSelectable: Boolean; -begin - Result := False; -end; - -function TPyEnvironmentProjectManagerMenu.GetName: string; -begin - Result := FName; -end; - -function TPyEnvironmentProjectManagerMenu.GetParent: string; -begin - Result := FParent; -end; - -function TPyEnvironmentProjectManagerMenu.GetPosition: Integer; -begin - Result := FPosition; -end; - -function TPyEnvironmentProjectManagerMenu.GetVerb: string; -begin - Result := FVerb; -end; - -function TPyEnvironmentProjectManagerMenu.PostExecute(const AMenuContextList: IInterfaceList): Boolean; -begin - Result := False; -end; - -function TPyEnvironmentProjectManagerMenu.PreExecute(const AMenuContextList: IInterfaceList): Boolean; -begin - Result := False; -end; - -procedure TPyEnvironmentProjectManagerMenu.SetCaption(const AValue: string); -begin -end; - -procedure TPyEnvironmentProjectManagerMenu.SetChecked(AValue: Boolean); -begin - FChecked := AValue; -end; - -procedure TPyEnvironmentProjectManagerMenu.SetEnabled(AValue: Boolean); -begin -end; - -procedure TPyEnvironmentProjectManagerMenu.SetHelpContext(AValue: Integer); -begin -end; - -procedure TPyEnvironmentProjectManagerMenu.SetIsMultiSelectable(AValue: Boolean); -begin -end; - -procedure TPyEnvironmentProjectManagerMenu.SetName(const AValue: string); -begin - FName := AValue; -end; - -procedure TPyEnvironmentProjectManagerMenu.SetParent(const AValue: string); -begin - FParent := AValue; -end; - -procedure TPyEnvironmentProjectManagerMenu.SetPosition(AValue: Integer); -begin - FPosition := AValue; -end; - -procedure TPyEnvironmentProjectManagerMenu.SetVerb(const AValue: string); -begin -end; - -{ TPyEnvironmentProjectManagerMenuSeparator } - -constructor TPyEnvironmentProjectManagerMenuSeparator.Create(const APosition: Integer); -begin - inherited Create('-', '', APosition); -end; - -{ TPyEnvironmentProjectManagerMenuEnablePythonEnvironment } - -constructor TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.Create( - const AProject: IOTAProject; const APosition: Integer); -begin - FIsPyEnvironmentEnabled := TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject]; - inherited Create(MENU_CAPTIONS[FIsPyEnvironmentEnabled], String.Empty, APosition, - procedure() begin - FIsPyEnvironmentEnabled := not FIsPyEnvironmentEnabled; - if not FIsPyEnvironmentEnabled then begin - TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject); - TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject] := String.Empty; - end; - TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject] := FIsPyEnvironmentEnabled; - end); -end; - -function TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.GetEnabled: Boolean; -begin - Result := FIsPyEnvironmentEnabled or TPyEnvironmentProjectDeploy.Found; -end; - -{ TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion } - -procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.AddSubItems( - const AProject: IOTAProject; const AList: IInterfaceList); -var - LPythonVersion: string; - LPosition: Integer; -begin - LPosition := 0; - for LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do begin - AList.Add( - TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.Create( - AProject, GetPosition() + LPosition, Self, LPythonVersion - )); - Inc(LPosition); - end; -end; - -constructor TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.Create( - const ASubMenuList: IInterfaceList; const AProject: IOTAProject; - const APosition: Integer); -begin - FIsPyEnvironmentEnabled := TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject]; - inherited Create(MENU_CAPTION, MENU_VERB, APosition); - AddSubItems(AProject, ASubMenuList); -end; - -function TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.GetEnabled: Boolean; -begin - Result := FIsPyEnvironmentEnabled; -end; - -{ TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem } - -constructor TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.Create( - const AProject: IOTAProject; const APosition: Integer; - const AParent: IOTALocalMenu; const APythonVersion: string); -begin - FProject := AProject; - FParent := AParent; - FPythonVersion := APythonVersion; - inherited Create(APythonVersion, String.Empty, APosition, - procedure() begin - if TPyEnvironmentProjectHelper.CurrentPythonVersion[FProject] <> FPythonVersion then begin - //Remove old version files - SetPythonVersion(FProject, false); - TPyEnvironmentProjectHelper.CurrentPythonVersion[FProject] := FPythonVersion; - //Add new version files - SetPythonVersion(FProject, true); - //Update Python version in Design-time components - SetDesginTimeCompsPythonVersion(FProject, FPythonVersion); - end; - end, String.Empty, AParent.GetVerb()); -end; - -function TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.GetChecked: boolean; -begin - Result := TPyEnvironmentProjectHelper.CurrentPythonVersion[FProject] = FPythonVersion; -end; - -function TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.GetEnabled: Boolean; -begin - Result := FParent.GetEnabled(); -end; - -procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.SetDeployFiles( - const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; - const APlatform: TPyEnvironmentProjectPlatform; const AEnabled: Boolean); -var - LDeployFile: TPyEnvironmentDeployFile; - LPythonVersion: string; -begin - if TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment(AProject) then - begin - LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; - if AEnabled and (APlatform in TPyEnvironmentProjectDeploy.SUPPORTED_PLATFORMS) then begin - for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(LPythonVersion, APlatform) do - TPyEnvironmentProjectHelper.AddDeployFile(AProject, AConfig, LDeployFile); - end else begin - for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(LPythonVersion, APlatform) do begin - TPyEnvironmentProjectHelper.RemoveDeployFile( - AProject, AConfig, APlatform, LDeployFile.LocalFileName, LDeployFile.RemotePath); - if LDeployFile.CopyToOutput then - TPyEnvironmentOTAHelper.TryRemoveOutputFile( - AProject, APlatform, AConfig, TPath.GetFileName(LDeployFile.LocalFileName)); - end; - end; - end; -end; - -procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.SetDesginTimeCompsPythonVersion( - const AProject: IOTAProject; const APythonVersion: string); -var - LIOTAEditor: IOTAFormEditor; - LIOTARootComponent: IOTAComponent; - I: Integer; - LIOTAComponent: IOTAComponent; - LDesigner: IDesigner; -begin - LIOTAEditor := GetActiveFormEditor(); - if Assigned(LIOTAEditor) then begin - LIOTARootComponent := LIOTAEditor.GetRootComponent(); - for I := 0 to LIOTARootComponent.GetComponentCount() - 1 do begin - LIOTAComponent := LIOTARootComponent.GetComponent(I); - if LIOTAComponent.GetComponentType().StartsWith('TPyEmbeddedEnvironment') then begin - LDesigner := GetActiveFormDesigner(); - if Assigned(LDesigner) then - //This will trigger the component editor and apply Python settings - LDesigner.SelectComponent(TPersistent(LIOTAComponent.GetComponentHandle())); - end; - end; - end; -end; - -procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.SetPythonVersion( - const AProject: IOTAProject; const AEnabled: Boolean); - - function SupportsPlatform(const APlatform: TPyEnvironmentProjectPlatform): Boolean; - var - LPlatformName: string; - LSupportedPlatform: string; - begin - if APlatform <> TPyEnvironmentProjectPlatform.Unknown then begin - LPlatformName := APlatform.ToString; - for LSupportedPlatform in AProject.SupportedPlatforms do - if SameText(LPlatformName, LSupportedPlatform) then - Exit(True); - end; - Result := False; - end; - -var - LPlatform: TPyEnvironmentProjectPlatform; - LConfig: TPyEnvironmentProjectConfig; - LProjectOptions: IOTAProjectOptions; -begin - for LPlatform := Low(TPyEnvironmentProjectPlatform) to High(TPyEnvironmentProjectPlatform) do - if SupportsPlatform(LPlatform) then - for LConfig := Low(TPyEnvironmentProjectConfig) to High(TPyEnvironmentProjectConfig) do - SetDeployFiles(AProject, LConfig, LPlatform, AEnabled); - - // Remove remaing files from old versions - if not AEnabled then - TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject); - - LProjectOptions := AProject.ProjectOptions; - if Assigned(LProjectOptions) then - LProjectOptions.ModifiedState := True; -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.ManagerMenu' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Enable/Disable and select Python in the project menu *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.ManagerMenu; + +interface + +uses + System.Classes, + System.SysUtils, + ToolsAPI, + DesignIntf, + PyEnvironment.Project.IDE.Deploy, + PyEnvironment.Project.IDE.Types; + +type + TPyEnvironmentProjectManagerMenu = class(TNotifierObject, IOTALocalMenu, IOTAProjectManagerMenu) + strict private + FCaption: string; + FExecuteProc: TProc; + FName: string; + FParent: string; + FPosition: Integer; + FVerb: string; + FChecked: boolean; + strict protected + { IOTALocalMenu } + function GetCaption: string; + function GetChecked: Boolean; virtual; + function GetEnabled: Boolean; virtual; + function GetHelpContext: Integer; + function GetName: string; + function GetParent: string; + function GetPosition: Integer; + function GetVerb: string; + procedure SetCaption(const AValue: string); + procedure SetChecked(AValue: Boolean); + procedure SetEnabled(AValue: Boolean); + procedure SetHelpContext(AValue: Integer); + procedure SetName(const AValue: string); + procedure SetParent(const AValue: string); + procedure SetPosition(AValue: Integer); + procedure SetVerb(const AValue: string); + { IOTAProjectManagerMenu } + function GetIsMultiSelectable: Boolean; + procedure Execute(const AMenuContextList: IInterfaceList); overload; + function PostExecute(const AMenuContextList: IInterfaceList): Boolean; + function PreExecute(const AMenuContextList: IInterfaceList): Boolean; + procedure SetIsMultiSelectable(AValue: Boolean); + public + constructor Create(const ACaption, AVerb: string; const APosition: Integer; + const AExecuteProc: TProc = nil; const AName: string = ''; + const AParent: string = ''; const AChecked: boolean = false); + end; + + TPyEnvironmentProjectManagerMenuSeparator = class(TPyEnvironmentProjectManagerMenu) + public + constructor Create(const APosition: Integer); reintroduce; + end; + + TPyEnvironmentProjectManagerMenuEnablePythonEnvironment = class(TPyEnvironmentProjectManagerMenu) + strict private + FIsPyEnvironmentEnabled: boolean; + strict protected + function GetEnabled: Boolean; override; + public const + MENU_CAPTIONS: array[Boolean] of string = ('Enable Python', 'Disable Python'); + public + constructor Create(const AProject: IOTAProject; const APosition: Integer); reintroduce; + end; + + TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion = class(TPyEnvironmentProjectManagerMenu) + strict private const + MENU_VERB = 'PythonEnvironmentVersion'; + MENU_CAPTION = 'Python Version'; + strict private + FIsPyEnvironmentEnabled: boolean; + strict protected + function GetEnabled: Boolean; override; + procedure AddSubItems(const AProject: IOTAProject; const AList: IInterfaceList); + public + constructor Create(const ASubMenuList: IInterfaceList; const AProject: IOTAProject; const APosition: Integer); reintroduce; + end; + + TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem = class(TPyEnvironmentProjectManagerMenu) + strict private + FProject: IOTAProject; + FParent: IOTALocalMenu; + FPythonVersion: string; + procedure SetDeployFiles(const AProject: IOTAProject; + const AConfig: TPyEnvironmentProjectConfig; + const APlatform: TPyEnvironmentProjectPlatform; + const AEnabled: Boolean); + procedure SetPythonVersion(const AProject: IOTAProject; + const AEnabled: Boolean); + procedure SetDesginTimeCompsPythonVersion(const AProject: IOTAProject; + const APythonVersion: string); + strict protected + function GetEnabled: Boolean; override; + function GetChecked: boolean; override; + public + constructor Create(const AProject: IOTAProject; const APosition: Integer; + const AParent: IOTALocalMenu; + const APythonVersion: string); reintroduce; + end; + +implementation + +uses + System.IOUtils, + PyEnvironment.Project.IDE.Helper; + +function GetActiveFormEditor: IOTAFormEditor; +var + LIOTAModule: IOTAModule; + LIOTAEditor: IOTAEditor; + I: Integer; +begin + LIOTAModule := (BorlandIDEServices as IOTAModuleServices).CurrentModule(); + if Assigned(LIOTAModule) then begin + for I := 0 to LIOTAModule.GetModuleFileCount - 1 do begin + LIOTAEditor := LIOTAModule.GetModuleFileEditor(I); + if Supports(LIOTAEditor, IOTAFormEditor, Result) then + Exit; + end; + end; + Result := nil; +end; + +function GetActiveFormDesigner: IDesigner; +var + LIOTAFormEditor: IOTAFormEditor; +begin + LIOTAFormEditor := GetActiveFormEditor(); + if Assigned(LIOTAFormEditor) then + Result := (LIOTAFormEditor as INTAFormEditor).FormDesigner + else + Result := nil; +end; + +{ TPyEnvironmentProjectManagerMenu } + +constructor TPyEnvironmentProjectManagerMenu.Create(const ACaption, AVerb: string; + const APosition: Integer; const AExecuteProc: TProc = nil; + const AName: string = ''; const AParent: string = ''; const AChecked: boolean = false); +begin + inherited Create; + FCaption := ACaption; + FName := AName; + FParent := AParent; + FPosition := APosition; + FVerb := AVerb; + FExecuteProc := AExecuteProc; + FChecked := AChecked; +end; + +procedure TPyEnvironmentProjectManagerMenu.Execute(const AMenuContextList: IInterfaceList); +begin + if Assigned(FExecuteProc) then + FExecuteProc; +end; + +function TPyEnvironmentProjectManagerMenu.GetCaption: string; +begin + Result := FCaption; +end; + +function TPyEnvironmentProjectManagerMenu.GetChecked: Boolean; +begin + Result := FChecked; +end; + +function TPyEnvironmentProjectManagerMenu.GetEnabled: Boolean; +begin + Result := True; +end; + +function TPyEnvironmentProjectManagerMenu.GetHelpContext: Integer; +begin + Result := 0; +end; + +function TPyEnvironmentProjectManagerMenu.GetIsMultiSelectable: Boolean; +begin + Result := False; +end; + +function TPyEnvironmentProjectManagerMenu.GetName: string; +begin + Result := FName; +end; + +function TPyEnvironmentProjectManagerMenu.GetParent: string; +begin + Result := FParent; +end; + +function TPyEnvironmentProjectManagerMenu.GetPosition: Integer; +begin + Result := FPosition; +end; + +function TPyEnvironmentProjectManagerMenu.GetVerb: string; +begin + Result := FVerb; +end; + +function TPyEnvironmentProjectManagerMenu.PostExecute(const AMenuContextList: IInterfaceList): Boolean; +begin + Result := False; +end; + +function TPyEnvironmentProjectManagerMenu.PreExecute(const AMenuContextList: IInterfaceList): Boolean; +begin + Result := False; +end; + +procedure TPyEnvironmentProjectManagerMenu.SetCaption(const AValue: string); +begin +end; + +procedure TPyEnvironmentProjectManagerMenu.SetChecked(AValue: Boolean); +begin + FChecked := AValue; +end; + +procedure TPyEnvironmentProjectManagerMenu.SetEnabled(AValue: Boolean); +begin +end; + +procedure TPyEnvironmentProjectManagerMenu.SetHelpContext(AValue: Integer); +begin +end; + +procedure TPyEnvironmentProjectManagerMenu.SetIsMultiSelectable(AValue: Boolean); +begin +end; + +procedure TPyEnvironmentProjectManagerMenu.SetName(const AValue: string); +begin + FName := AValue; +end; + +procedure TPyEnvironmentProjectManagerMenu.SetParent(const AValue: string); +begin + FParent := AValue; +end; + +procedure TPyEnvironmentProjectManagerMenu.SetPosition(AValue: Integer); +begin + FPosition := AValue; +end; + +procedure TPyEnvironmentProjectManagerMenu.SetVerb(const AValue: string); +begin +end; + +{ TPyEnvironmentProjectManagerMenuSeparator } + +constructor TPyEnvironmentProjectManagerMenuSeparator.Create(const APosition: Integer); +begin + inherited Create('-', '', APosition); +end; + +{ TPyEnvironmentProjectManagerMenuEnablePythonEnvironment } + +constructor TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.Create( + const AProject: IOTAProject; const APosition: Integer); +begin + FIsPyEnvironmentEnabled := TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject]; + inherited Create(MENU_CAPTIONS[FIsPyEnvironmentEnabled], String.Empty, APosition, + procedure() begin + FIsPyEnvironmentEnabled := not FIsPyEnvironmentEnabled; + if not FIsPyEnvironmentEnabled then begin + TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject); + TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject] := String.Empty; + end; + TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject] := FIsPyEnvironmentEnabled; + end); +end; + +function TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.GetEnabled: Boolean; +begin + Result := FIsPyEnvironmentEnabled or TPyEnvironmentProjectDeploy.Found; +end; + +{ TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion } + +procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.AddSubItems( + const AProject: IOTAProject; const AList: IInterfaceList); +var + LPythonVersion: string; + LPosition: Integer; +begin + LPosition := 0; + for LPythonVersion in TPyEnvironmentProjectDeploy.PYTHON_VERSIONS do begin + AList.Add( + TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.Create( + AProject, GetPosition() + LPosition, Self, LPythonVersion + )); + Inc(LPosition); + end; +end; + +constructor TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.Create( + const ASubMenuList: IInterfaceList; const AProject: IOTAProject; + const APosition: Integer); +begin + FIsPyEnvironmentEnabled := TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject]; + inherited Create(MENU_CAPTION, MENU_VERB, APosition); + AddSubItems(AProject, ASubMenuList); +end; + +function TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.GetEnabled: Boolean; +begin + Result := FIsPyEnvironmentEnabled; +end; + +{ TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem } + +constructor TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.Create( + const AProject: IOTAProject; const APosition: Integer; + const AParent: IOTALocalMenu; const APythonVersion: string); +begin + FProject := AProject; + FParent := AParent; + FPythonVersion := APythonVersion; + inherited Create(APythonVersion, String.Empty, APosition, + procedure() begin + if TPyEnvironmentProjectHelper.CurrentPythonVersion[FProject] <> FPythonVersion then begin + //Remove old version files + SetPythonVersion(FProject, false); + TPyEnvironmentProjectHelper.CurrentPythonVersion[FProject] := FPythonVersion; + //Add new version files + SetPythonVersion(FProject, true); + //Update Python version in Design-time components + SetDesginTimeCompsPythonVersion(FProject, FPythonVersion); + end; + end, String.Empty, AParent.GetVerb()); +end; + +function TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.GetChecked: boolean; +begin + Result := TPyEnvironmentProjectHelper.CurrentPythonVersion[FProject] = FPythonVersion; +end; + +function TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.GetEnabled: Boolean; +begin + Result := FParent.GetEnabled(); +end; + +procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.SetDeployFiles( + const AProject: IOTAProject; const AConfig: TPyEnvironmentProjectConfig; + const APlatform: TPyEnvironmentProjectPlatform; const AEnabled: Boolean); +var + LDeployFile: TPyEnvironmentDeployFile; + LPythonVersion: string; +begin + if TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment(AProject) then + begin + LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; + if AEnabled and (APlatform in TPyEnvironmentProjectDeploy.SUPPORTED_PLATFORMS) then begin + for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(AProject.FileName, LPythonVersion, APlatform) do + TPyEnvironmentProjectHelper.AddDeployFile(AProject, AConfig, LDeployFile); + end else begin + for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(AProject.FileName, LPythonVersion, APlatform) do begin + TPyEnvironmentProjectHelper.RemoveDeployFile( + AProject, AConfig, APlatform, LDeployFile.LocalFileName, LDeployFile.RemotePath); + if LDeployFile.CopyToOutput then + TPyEnvironmentOTAHelper.TryRemoveOutputFile( + AProject, APlatform, AConfig, TPath.GetFileName(LDeployFile.LocalFileName)); + end; + end; + end; +end; + +procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.SetDesginTimeCompsPythonVersion( + const AProject: IOTAProject; const APythonVersion: string); +var + LIOTAEditor: IOTAFormEditor; + LIOTARootComponent: IOTAComponent; + I: Integer; + LIOTAComponent: IOTAComponent; + LDesigner: IDesigner; +begin + LIOTAEditor := GetActiveFormEditor(); + if Assigned(LIOTAEditor) then begin + LIOTARootComponent := LIOTAEditor.GetRootComponent(); + for I := 0 to LIOTARootComponent.GetComponentCount() - 1 do begin + LIOTAComponent := LIOTARootComponent.GetComponent(I); + if LIOTAComponent.GetComponentType().StartsWith('TPyEmbeddedEnvironment') then begin + LDesigner := GetActiveFormDesigner(); + if Assigned(LDesigner) then + //This will trigger the component editor and apply Python settings + LDesigner.SelectComponent(TPersistent(LIOTAComponent.GetComponentHandle())); + end; + end; + end; +end; + +procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersionSubItem.SetPythonVersion( + const AProject: IOTAProject; const AEnabled: Boolean); + + function SupportsPlatform(const APlatform: TPyEnvironmentProjectPlatform): Boolean; + var + LPlatformName: string; + LSupportedPlatform: string; + begin + if APlatform <> TPyEnvironmentProjectPlatform.Unknown then begin + LPlatformName := APlatform.ToString; + for LSupportedPlatform in AProject.SupportedPlatforms do + if SameText(LPlatformName, LSupportedPlatform) then + Exit(True); + end; + Result := False; + end; + +var + LPlatform: TPyEnvironmentProjectPlatform; + LConfig: TPyEnvironmentProjectConfig; + LProjectOptions: IOTAProjectOptions; +begin + for LPlatform := Low(TPyEnvironmentProjectPlatform) to High(TPyEnvironmentProjectPlatform) do + if SupportsPlatform(LPlatform) then + for LConfig := Low(TPyEnvironmentProjectConfig) to High(TPyEnvironmentProjectConfig) do + SetDeployFiles(AProject, LConfig, LPlatform, AEnabled); + + // Remove remaing files from old versions + if not AEnabled then + TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject); + + LProjectOptions := AProject.ProjectOptions; + if Assigned(LProjectOptions) then + LProjectOptions.ModifiedState := True; +end; + +end. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Menu.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Menu.pas index 0ded6f3..20eab65 100644 --- a/src/Project/IDE/PyEnvironment.Project.IDE.Menu.pas +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Menu.pas @@ -1,236 +1,236 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Project.IDE.Menu' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Python project menu creator *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Project.IDE.Menu; - -interface - -uses - System.Classes, - ToolsAPI; - -type - TPyEnvironmentProjectMenuCreatorNotifier = class(TNotifierObject, IOTANotifier, IOTAProjectMenuItemCreatorNotifier) - strict private - class var FNotifierIndex: Integer; - class constructor Create; - class destructor Destroy; - - { IOTAProjectMenuItemCreatorNotifier } - procedure AddMenu(const AProject: IOTAProject; const AIdentList: TStrings; - const AProjectManagerMenuList: IInterfaceList; AIsMultiSelect: Boolean); - public - class procedure Register; static; - end; - - TPyEnvironmenCompileNotifier = class(TInterfacedObject, IOTACompileNotifier) - strict private - const - UnsupportedPlatformMessage = - 'The Python Environment does not support the platform %s in this RAD Studio version.' + sLineBreak + sLineBreak + - 'To avoid problems, disable Python Environment in this project (Project menu > %s) or, if you want to disable it just in ' + - 'a specific platform, set the define directive "%s" in the project settings of this platform. In both cases, ' + - 'be sure you are not using any Python Environment units, otherwise you will get "runtime error" on startup of your application.'; - class var FNotifierIndex: Integer; - class constructor Create; - class destructor Destroy; - { IOTACompileNotifier } - procedure ProjectCompileStarted(const AProject: IOTAProject; AMode: TOTACompileMode); - procedure ProjectCompileFinished(const AProject: IOTAProject; AResult: TOTACompileResult); - procedure ProjectGroupCompileStarted(AMode: TOTACompileMode); - procedure ProjectGroupCompileFinished(AResult: TOTACompileResult); - public - class procedure Register; static; - end; - -implementation - -uses - System.SysUtils, System.IOUtils, - Vcl.Dialogs, - PyEnvironment.Project.IDE.Deploy, - PyEnvironment.Project.IDE.Helper, - PyEnvironment.Project.IDE.Types, - PyEnvironment.Project.IDE.ManagerMenu; - -const - INVALID_MENU_INDEX = -1; - -{ TPyEnvironmentProjectMenuCreatorNotifier } - -class constructor TPyEnvironmentProjectMenuCreatorNotifier.Create; -begin - FNotifierIndex := INVALID_MENU_INDEX; -end; - -class destructor TPyEnvironmentProjectMenuCreatorNotifier.Destroy; -var - LProjectManager: IOTAProjectManager; -begin - if (FNotifierIndex > INVALID_MENU_INDEX) - and Supports(BorlandIDEServices, IOTAProjectManager, LProjectManager) then - LProjectManager.RemoveMenuItemCreatorNotifier(FNotifierIndex); -end; - -class procedure TPyEnvironmentProjectMenuCreatorNotifier.Register; -var - LProjectManager: IOTAProjectManager; -begin - if (FNotifierIndex <= INVALID_MENU_INDEX) - and Supports(BorlandIDEServices, IOTAProjectManager, LProjectManager) then - FNotifierIndex := LProjectManager.AddMenuItemCreatorNotifier( - TPyEnvironmentProjectMenuCreatorNotifier.Create()); -end; - -procedure TPyEnvironmentProjectMenuCreatorNotifier.AddMenu( - const AProject: IOTAProject; const AIdentList: TStrings; - const AProjectManagerMenuList: IInterfaceList; AIsMultiSelect: Boolean); -begin - if (not AIsMultiSelect) - and (AIdentList.IndexOf(sProjectContainer) <> -1) - and Assigned(AProjectManagerMenuList) - and TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment(AProject) then - begin - AProjectManagerMenuList.Add( - TPyEnvironmentProjectManagerMenuSeparator.Create(pmmpRunNoDebug + 10)); - AProjectManagerMenuList.Add( - TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.Create( - AProject, pmmpRunNoDebug + 20)); - AProjectManagerMenuList.Add( - TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.Create( - AProjectManagerMenuList, AProject, pmmpRunNoDebug + 30)); - end; -end; - -{ TPyEnvironmenCompileNotifier } - -class constructor TPyEnvironmenCompileNotifier.Create; -begin - FNotifierIndex := INVALID_MENU_INDEX; -end; - -class destructor TPyEnvironmenCompileNotifier.Destroy; -var - LCompileServices: IOTACompileServices; -begin - if (FNotifierIndex > INVALID_MENU_INDEX) - and Supports(BorlandIDEServices, IOTACompileServices, LCompileServices) then - LCompileServices.RemoveNotifier(FNotifierIndex); -end; - -procedure TPyEnvironmenCompileNotifier.ProjectCompileFinished( - const AProject: IOTAProject; AResult: TOTACompileResult); -begin - // -end; - -procedure TPyEnvironmenCompileNotifier.ProjectCompileStarted( - const AProject: IOTAProject; AMode: TOTACompileMode); -var - LPlatform: TPyEnvironmentProjectPlatform; - LConfig: TPyEnvironmentProjectConfig; - LDeployFile: TPyEnvironmentDeployFile; - LPythonVersion: string; -begin - if TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment(AProject) then - begin - if Assigned(AProject) then - LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform) - else - LPlatform := TPyEnvironmentProjectPlatform.Unknown; - if LPlatform = TPyEnvironmentProjectPlatform.Unknown then - Exit; - - if (AMode in [TOTACompileMode.cmOTAMake, TOTACompileMode.cmOTABuild]) and - TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject] and TPyEnvironmentProjectDeploy.Found then - begin - LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; - LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); - if TPyEnvironmentProjectHelper.IsPyEnvironmentDefinedForPlatform(AProject, LPlatform, LConfig) then begin - if LPlatform in TPyEnvironmentProjectDeploy.SUPPORTED_PLATFORMS then begin - TPyEnvironmentProjectHelper.RemoveUnexpectedDeployFilesOfClass( - AProject, LConfig, LPlatform, TPyEnvironmentProjectDeploy.GetDeployFiles(LPythonVersion, LPlatform)); - for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(LPythonVersion, LPlatform) do begin - if LDeployFile.CopyToOutput then begin - Assert(LDeployFile.LocalFileName <> ''); - TPyEnvironmentOTAHelper.TryCopyFileToOutputPathOfActiveBuild(AProject, - TPath.Combine(TPyEnvironmentProjectDeploy.AbsolutePath, LDeployFile.LocalFileName)); - end; - TPyEnvironmentProjectHelper.AddDeployFile(AProject, LConfig, LDeployFile); - end; - end else begin - for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(LPythonVersion, LPlatform) do - TPyEnvironmentProjectHelper.RemoveDeployFile( - AProject, LConfig, LPlatform, LDeployFile.LocalFileName, LDeployFile.RemotePath); - TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject, LConfig, LPlatform); - Showmessage(Format(UnsupportedPlatformMessage, [AProject.CurrentPlatform, - TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.MENU_CAPTIONS[True], - TPyEnvironmentProjectDeploy.PROJECT_NO_USE_PYTHON])); - end; - end else begin - for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(LPythonVersion, LPlatform) do - TPyEnvironmentProjectHelper.RemoveDeployFile( - AProject, LConfig, LPlatform, LDeployFile.LocalFileName, LDeployFile.RemotePath); - TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject, LConfig, LPlatform); - end; - end - {$IF CompilerVersion >= 35} - else if (AMode = TOTACompileMode.cmOTAClean) and TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject] then begin - LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; - for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(LPythonVersion, LPlatform) do - if LDeployFile.CopyToOutput then - TPyEnvironmentOTAHelper.TryRemoveOutputFileOfActiveBuild(AProject, TPath.GetFileName(LDeployFile.LocalFileName)); - end; - {$ENDIF} - end; -end; - -procedure TPyEnvironmenCompileNotifier.ProjectGroupCompileFinished( - AResult: TOTACompileResult); -begin - -end; - -procedure TPyEnvironmenCompileNotifier.ProjectGroupCompileStarted( - AMode: TOTACompileMode); -begin - -end; - -class procedure TPyEnvironmenCompileNotifier.Register; -var - LCompileServices: IOTACompileServices; -begin - if (FNotifierIndex <= INVALID_MENU_INDEX) - and Supports(BorlandIDEServices, IOTACompileServices, LCompileServices) then - FNotifierIndex := LCompileServices.AddNotifier(TPyEnvironmenCompileNotifier.Create); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Menu' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Python project menu creator *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Menu; + +interface + +uses + System.Classes, + ToolsAPI; + +type + TPyEnvironmentProjectMenuCreatorNotifier = class(TNotifierObject, IOTANotifier, IOTAProjectMenuItemCreatorNotifier) + strict private + class var FNotifierIndex: Integer; + class constructor Create; + class destructor Destroy; + + { IOTAProjectMenuItemCreatorNotifier } + procedure AddMenu(const AProject: IOTAProject; const AIdentList: TStrings; + const AProjectManagerMenuList: IInterfaceList; AIsMultiSelect: Boolean); + public + class procedure Register; static; + end; + + TPyEnvironmenCompileNotifier = class(TInterfacedObject, IOTACompileNotifier) + strict private + const + UnsupportedPlatformMessage = + 'The Python Environment does not support the platform %s in this RAD Studio version.' + sLineBreak + sLineBreak + + 'To avoid problems, disable Python Environment in this project (Project menu > %s) or, if you want to disable it just in ' + + 'a specific platform, set the define directive "%s" in the project settings of this platform. In both cases, ' + + 'be sure you are not using any Python Environment units, otherwise you will get "runtime error" on startup of your application.'; + class var FNotifierIndex: Integer; + class constructor Create; + class destructor Destroy; + { IOTACompileNotifier } + procedure ProjectCompileStarted(const AProject: IOTAProject; AMode: TOTACompileMode); + procedure ProjectCompileFinished(const AProject: IOTAProject; AResult: TOTACompileResult); + procedure ProjectGroupCompileStarted(AMode: TOTACompileMode); + procedure ProjectGroupCompileFinished(AResult: TOTACompileResult); + public + class procedure Register; static; + end; + +implementation + +uses + System.SysUtils, System.IOUtils, + Vcl.Dialogs, + PyEnvironment.Project.IDE.Deploy, + PyEnvironment.Project.IDE.Helper, + PyEnvironment.Project.IDE.Types, + PyEnvironment.Project.IDE.ManagerMenu; + +const + INVALID_MENU_INDEX = -1; + +{ TPyEnvironmentProjectMenuCreatorNotifier } + +class constructor TPyEnvironmentProjectMenuCreatorNotifier.Create; +begin + FNotifierIndex := INVALID_MENU_INDEX; +end; + +class destructor TPyEnvironmentProjectMenuCreatorNotifier.Destroy; +var + LProjectManager: IOTAProjectManager; +begin + if (FNotifierIndex > INVALID_MENU_INDEX) + and Supports(BorlandIDEServices, IOTAProjectManager, LProjectManager) then + LProjectManager.RemoveMenuItemCreatorNotifier(FNotifierIndex); +end; + +class procedure TPyEnvironmentProjectMenuCreatorNotifier.Register; +var + LProjectManager: IOTAProjectManager; +begin + if (FNotifierIndex <= INVALID_MENU_INDEX) + and Supports(BorlandIDEServices, IOTAProjectManager, LProjectManager) then + FNotifierIndex := LProjectManager.AddMenuItemCreatorNotifier( + TPyEnvironmentProjectMenuCreatorNotifier.Create()); +end; + +procedure TPyEnvironmentProjectMenuCreatorNotifier.AddMenu( + const AProject: IOTAProject; const AIdentList: TStrings; + const AProjectManagerMenuList: IInterfaceList; AIsMultiSelect: Boolean); +begin + if (not AIsMultiSelect) + and (AIdentList.IndexOf(sProjectContainer) <> -1) + and Assigned(AProjectManagerMenuList) + and TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment(AProject) then + begin + AProjectManagerMenuList.Add( + TPyEnvironmentProjectManagerMenuSeparator.Create(pmmpRunNoDebug + 10)); + AProjectManagerMenuList.Add( + TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.Create( + AProject, pmmpRunNoDebug + 20)); + AProjectManagerMenuList.Add( + TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.Create( + AProjectManagerMenuList, AProject, pmmpRunNoDebug + 30)); + end; +end; + +{ TPyEnvironmenCompileNotifier } + +class constructor TPyEnvironmenCompileNotifier.Create; +begin + FNotifierIndex := INVALID_MENU_INDEX; +end; + +class destructor TPyEnvironmenCompileNotifier.Destroy; +var + LCompileServices: IOTACompileServices; +begin + if (FNotifierIndex > INVALID_MENU_INDEX) + and Supports(BorlandIDEServices, IOTACompileServices, LCompileServices) then + LCompileServices.RemoveNotifier(FNotifierIndex); +end; + +procedure TPyEnvironmenCompileNotifier.ProjectCompileFinished( + const AProject: IOTAProject; AResult: TOTACompileResult); +begin + // +end; + +procedure TPyEnvironmenCompileNotifier.ProjectCompileStarted( + const AProject: IOTAProject; AMode: TOTACompileMode); +var + LPlatform: TPyEnvironmentProjectPlatform; + LConfig: TPyEnvironmentProjectConfig; + LDeployFile: TPyEnvironmentDeployFile; + LPythonVersion: string; +begin + if TPyEnvironmentProjectHelper.SupportsEnvironmentDeployment(AProject) then + begin + if Assigned(AProject) then + LPlatform := TPyEnvironmentProjectPlatform.FromString(AProject.CurrentPlatform) + else + LPlatform := TPyEnvironmentProjectPlatform.Unknown; + if LPlatform = TPyEnvironmentProjectPlatform.Unknown then + Exit; + + if (AMode in [TOTACompileMode.cmOTAMake, TOTACompileMode.cmOTABuild]) and + TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject] and TPyEnvironmentProjectDeploy.Found then + begin + LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; + LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); + if TPyEnvironmentProjectHelper.IsPyEnvironmentDefinedForPlatform(AProject, LPlatform, LConfig) then begin + if LPlatform in TPyEnvironmentProjectDeploy.SUPPORTED_PLATFORMS then begin + TPyEnvironmentProjectHelper.RemoveUnexpectedDeployFilesOfClass( + AProject, LConfig, LPlatform, TPyEnvironmentProjectDeploy.GetDeployFiles(AProject.FileName, LPythonVersion, LPlatform)); + for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(AProject.FileName, LPythonVersion, LPlatform) do begin + if LDeployFile.CopyToOutput then begin + Assert(LDeployFile.LocalFileName <> ''); + TPyEnvironmentOTAHelper.TryCopyFileToOutputPathOfActiveBuild(AProject, + TPath.Combine(TPyEnvironmentProjectDeploy.AbsolutePath, LDeployFile.LocalFileName)); + end; + TPyEnvironmentProjectHelper.AddDeployFile(AProject, LConfig, LDeployFile); + end; + end else begin + for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(AProject.FileName, LPythonVersion, LPlatform) do + TPyEnvironmentProjectHelper.RemoveDeployFile( + AProject, LConfig, LPlatform, LDeployFile.LocalFileName, LDeployFile.RemotePath); + TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject, LConfig, LPlatform); + Showmessage(Format(UnsupportedPlatformMessage, [AProject.CurrentPlatform, + TPyEnvironmentProjectManagerMenuEnablePythonEnvironment.MENU_CAPTIONS[True], + TPyEnvironmentProjectDeploy.PROJECT_NO_USE_PYTHON])); + end; + end else begin + for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(AProject.FileName, LPythonVersion, LPlatform) do + TPyEnvironmentProjectHelper.RemoveDeployFile( + AProject, LConfig, LPlatform, LDeployFile.LocalFileName, LDeployFile.RemotePath); + TPyEnvironmentProjectHelper.RemoveDeployFilesOfClass(AProject, LConfig, LPlatform); + end; + end + {$IF CompilerVersion >= 35} + else if (AMode = TOTACompileMode.cmOTAClean) and TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[AProject] then begin + LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; + for LDeployFile in TPyEnvironmentProjectDeploy.GetDeployFiles(AProject.FileName, LPythonVersion, LPlatform) do + if LDeployFile.CopyToOutput then + TPyEnvironmentOTAHelper.TryRemoveOutputFileOfActiveBuild(AProject, TPath.GetFileName(LDeployFile.LocalFileName)); + end; + {$ENDIF} + end; +end; + +procedure TPyEnvironmenCompileNotifier.ProjectGroupCompileFinished( + AResult: TOTACompileResult); +begin + +end; + +procedure TPyEnvironmenCompileNotifier.ProjectGroupCompileStarted( + AMode: TOTACompileMode); +begin + +end; + +class procedure TPyEnvironmenCompileNotifier.Register; +var + LCompileServices: IOTACompileServices; +begin + if (FNotifierIndex <= INVALID_MENU_INDEX) + and Supports(BorlandIDEServices, IOTACompileServices, LCompileServices) then + FNotifierIndex := LCompileServices.AddNotifier(TPyEnvironmenCompileNotifier.Create); +end; + +end. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas index 38b9f39..cea6941 100644 --- a/src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas @@ -1,48 +1,48 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Project.IDE.Deploy' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Python project menu registration *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Project.IDE.Registration; - -interface - -procedure Register(); - -implementation - -uses - PyEnvironment.Project.IDE.Menu; - -procedure Register(); -begin - TPyEnvironmentProjectMenuCreatorNotifier.Register(); - TPyEnvironmenCompileNotifier.Register(); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Python project menu registration *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Registration; + +interface + +procedure Register(); + +implementation + +uses + PyEnvironment.Project.IDE.Menu; + +procedure Register(); +begin + TPyEnvironmentProjectMenuCreatorNotifier.Register(); + TPyEnvironmenCompileNotifier.Register(); +end; + +end. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Types.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Types.pas index 37895d2..d9b702a 100644 --- a/src/Project/IDE/PyEnvironment.Project.IDE.Types.pas +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Types.pas @@ -1,139 +1,146 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Project.IDE.Deploy' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Python project menu types *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Project.IDE.Types; - -interface - -uses - DeploymentAPI; - -type - TPyEnvironmentProjectConfig = (Release, Debug); - TPyEnvironmentProjectConfigs = set of TPyEnvironmentProjectConfig; - TPyEnvironmentProjectPlatform = (Unknown, Win32, Win64, Android, Android64, iOSDevice32, iOSDevice64, iOSSimulator, OSX64, OSXARM64, Linux64); - - TPyEvironmentProjectConfigHelper = record helper for TPyEnvironmentProjectConfig - function ToString: string; - class function FromString(const AText: string): TPyEnvironmentProjectConfig; static; - end; - - TPyEvironmentProjectPlatformHelper = record helper for TPyEnvironmentProjectPlatform - function ToString: string; - class function FromString(const AText: string): TPyEnvironmentProjectPlatform; static; - end; - - TPyEnvironmentDeployFile = record - Configs: TPyEnvironmentProjectConfigs; - &Platform: TPyEnvironmentProjectPlatform; - LocalFileName: string; - RemotePath: string; - CopyToOutput: Boolean; - Required: Boolean; - Operation: TDeployOperation; - Condition: string; - - constructor Create(const AConfigs: TPyEnvironmentProjectConfigs; - const APlatform: TPyEnvironmentProjectPlatform; - const ALocalFileName, ARemotePath: string; - const ACopyToOutput, ARequired: boolean; - const AOperation: TDeployOperation; const ACondition: string); overload; - - constructor Create(const APlatform: TPyEnvironmentProjectPlatform; - const ALocalFileName, ARemotePath: string; - const ACopyToOutput, ARequired: boolean; - const AOperation: TDeployOperation; const ACondition: string); overload; - end; - -implementation - -uses - TypInfo; - -{ TPyEnvironmentDeployFile } - -constructor TPyEnvironmentDeployFile.Create( - const APlatform: TPyEnvironmentProjectPlatform; const ALocalFileName, - ARemotePath: string; const ACopyToOutput, ARequired: boolean; - const AOperation: TDeployOperation; const ACondition: string); -begin - Create([Release, Debug], APlatform, ALocalFileName, ARemotePath, - ACopyToOutput, ARequired, AOperation, ACondition); -end; - -constructor TPyEnvironmentDeployFile.Create( - const AConfigs: TPyEnvironmentProjectConfigs; - const APlatform: TPyEnvironmentProjectPlatform; const ALocalFileName, - ARemotePath: string; const ACopyToOutput, ARequired: boolean; - const AOperation: TDeployOperation; const ACondition: string); -begin - Configs := AConfigs; - &Platform := APlatform; - LocalFileName := ALocalFilename; - RemotePath := ARemotePath; - CopyToOutput := ACopyToOutput; - Required := ARequired; - Operation := AOperation; - Condition := ACondition; -end; - -{ TPyEvironmentProjectConfigHelper } - -class function TPyEvironmentProjectConfigHelper.FromString( - const AText: string): TPyEnvironmentProjectConfig; -begin - Result := TPyEnvironmentProjectConfig(GetEnumValue(TypeInfo(TPyEnvironmentProjectConfig), AText)); -end; - -function TPyEvironmentProjectConfigHelper.ToString: string; -begin - Result := GetEnumName(TypeInfo(TPyEnvironmentProjectConfig), Ord(Self)); -end; - -{ TPyEvironmentProjectPlatformHelper } - -class function TPyEvironmentProjectPlatformHelper.FromString( - const AText: string): TPyEnvironmentProjectPlatform; -var - LEnumValue: Integer; -begin - LEnumValue := GetEnumValue(TypeInfo(TPyEnvironmentProjectPlatform), AText); - if LEnumValue = -1 then - Result := TPyEnvironmentProjectPlatform.Unknown - else - Result := TPyEnvironmentProjectPlatform(GetEnumValue(TypeInfo(TPyEnvironmentProjectPlatform), AText)); -end; - -function TPyEvironmentProjectPlatformHelper.ToString: string; -begin - Result := GetEnumName(TypeInfo(TPyEnvironmentProjectPlatform), Ord(Self)); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Project.IDE.Deploy' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Python project menu types *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Project.IDE.Types; + +interface + +uses + System.Generics.Collections, + DeploymentAPI; + +type + TPyEnvironmentProjectConfig = (Release, Debug); + TPyEnvironmentProjectConfigs = set of TPyEnvironmentProjectConfig; + TPyEnvironmentProjectPlatform = (Unknown, Win32, Win64, Android, Android64, iOSDevice32, iOSDevice64, iOSSimulator, iOSSimARM64, OSX64, OSXARM64, Linux64); + + TPyEvironmentProjectConfigHelper = record helper for TPyEnvironmentProjectConfig + function ToString: string; + class function FromString(const AText: string): TPyEnvironmentProjectConfig; static; + end; + + TPyEvironmentProjectPlatformHelper = record helper for TPyEnvironmentProjectPlatform + function ToString: string; + class function FromString(const AText: string): TPyEnvironmentProjectPlatform; static; + end; + + TPyEnvironmentDeployFile = record + Configs: TPyEnvironmentProjectConfigs; + &Platform: TPyEnvironmentProjectPlatform; + LocalFileName: string; + RemotePath: string; + CopyToOutput: Boolean; + Required: Boolean; + Operation: TDeployOperation; + Condition: string; + UpdateLocalFileName: boolean; + + constructor Create(const AConfigs: TPyEnvironmentProjectConfigs; + const APlatform: TPyEnvironmentProjectPlatform; + const ALocalFileName, ARemotePath: string; + const ACopyToOutput, ARequired: boolean; + const AOperation: TDeployOperation; const ACondition: string; + const AUpdateLocalFileName: boolean = true); overload; + + constructor Create(const APlatform: TPyEnvironmentProjectPlatform; + const ALocalFileName, ARemotePath: string; + const ACopyToOutput, ARequired: boolean; + const AOperation: TDeployOperation; const ACondition: string; + const AUpdateLocalFileName: boolean = true); overload; + end; + +implementation + +uses + TypInfo; + +{ TPyEnvironmentDeployFile } + +constructor TPyEnvironmentDeployFile.Create( + const APlatform: TPyEnvironmentProjectPlatform; const ALocalFileName, + ARemotePath: string; const ACopyToOutput, ARequired: boolean; + const AOperation: TDeployOperation; const ACondition: string; + const AUpdateLocalFileName: boolean); +begin + Create([Release, Debug], APlatform, ALocalFileName, ARemotePath, + ACopyToOutput, ARequired, AOperation, ACondition, AUpdateLocalFileName); +end; + +constructor TPyEnvironmentDeployFile.Create( + const AConfigs: TPyEnvironmentProjectConfigs; + const APlatform: TPyEnvironmentProjectPlatform; const ALocalFileName, + ARemotePath: string; const ACopyToOutput, ARequired: boolean; + const AOperation: TDeployOperation; const ACondition: string; + const AUpdateLocalFileName: boolean); +begin + Configs := AConfigs; + &Platform := APlatform; + LocalFileName := ALocalFilename; + RemotePath := ARemotePath; + CopyToOutput := ACopyToOutput; + Required := ARequired; + Operation := AOperation; + Condition := ACondition; + UpdateLocalFileName := AUpdateLocalFileName; +end; + +{ TPyEvironmentProjectConfigHelper } + +class function TPyEvironmentProjectConfigHelper.FromString( + const AText: string): TPyEnvironmentProjectConfig; +begin + Result := TPyEnvironmentProjectConfig(GetEnumValue(TypeInfo(TPyEnvironmentProjectConfig), AText)); +end; + +function TPyEvironmentProjectConfigHelper.ToString: string; +begin + Result := GetEnumName(TypeInfo(TPyEnvironmentProjectConfig), Ord(Self)); +end; + +{ TPyEvironmentProjectPlatformHelper } + +class function TPyEvironmentProjectPlatformHelper.FromString( + const AText: string): TPyEnvironmentProjectPlatform; +var + LEnumValue: Integer; +begin + LEnumValue := GetEnumValue(TypeInfo(TPyEnvironmentProjectPlatform), AText); + if LEnumValue = -1 then + Result := TPyEnvironmentProjectPlatform.Unknown + else + Result := TPyEnvironmentProjectPlatform(GetEnumValue(TypeInfo(TPyEnvironmentProjectPlatform), AText)); +end; + +function TPyEvironmentProjectPlatformHelper.ToString: string; +begin + Result := GetEnumName(TypeInfo(TPyEnvironmentProjectPlatform), Ord(Self)); +end; + +end. diff --git a/src/PyEnvionment.Editors.pas b/src/PyEnvionment.Editors.pas index f91d33c..24246b7 100644 --- a/src/PyEnvionment.Editors.pas +++ b/src/PyEnvionment.Editors.pas @@ -1,106 +1,106 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Editors' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Component editors *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvionment.Editors; - -interface - -uses - System.SysUtils, Classes, DesignIntf, DesignEditors; - -type - TPyEnvironmentEmbeddedEditor = class(TComponentEditor) - public - constructor Create(AComponent: TComponent; ADesigner: IDesigner); override; - end; - - TPyEnvironmentEmbeddedPythonVersionProperty = class (TStringProperty) - public - procedure SetValue(const Value: string); override; - end; - -procedure Register(); - -implementation - -uses - ToolsAPI, - PyEnvironment.Project.IDE.Helper, - PyEnvironment.Embeddable; - -procedure Register(); -begin - RegisterComponentEditor(TPyEmbeddedEnvironment, TPyEnvironmentEmbeddedEditor); - RegisterPropertyEditor(TypeInfo(string), TPyEmbeddedEnvironment, 'PythonVersion', TPyEnvironmentEmbeddedPythonVersionProperty); -end; - -{ TPyEnvironmentEmbeddedPythonVersionProperty } - -procedure TPyEnvironmentEmbeddedPythonVersionProperty.SetValue( - const Value: string); -var - LProject: IOTAProject; -begin - LProject := GetActiveProject(); - if Assigned(LProject) and TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[LProject] then begin - inherited SetValue(TPyEnvironmentProjectHelper.CurrentPythonVersion[LProject]) - end else - inherited; -end; - -{ TPyEnvironmentEmbeddedEditor } - -constructor TPyEnvironmentEmbeddedEditor.Create(AComponent: TComponent; - ADesigner: IDesigner); -var - LProject: IOTAProject; - LEnvironment: TPyEmbeddedEnvironment; - LUpdateNeeded: Boolean; -begin - inherited; - LProject := GetActiveProject(); - if TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[LProject] then begin - LEnvironment := (Self.Component as TPyEmbeddedEnvironment); - LUpdateNeeded := false; - if (LEnvironment.PythonVersion <> TPyEnvironmentProjectHelper.CurrentPythonVersion[LProject]) - or LEnvironment.Scanner.AutoScan = false - or (LEnvironment.Scanner.ScanRule <> TPyEmbeddedEnvironment.TScanRule.srFileName) then - LUpdateNeeded := true; - - LEnvironment.PythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[LProject]; - LEnvironment.Scanner.AutoScan := true; - LEnvironment.Scanner.ScanRule := TPyEmbeddedEnvironment.TScanRule.srFileName; - - if LUpdateNeeded then - Designer.Modified(); - end; -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Editors' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Component editors *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvionment.Editors; + +interface + +uses + System.SysUtils, Classes, DesignIntf, DesignEditors; + +type + TPyEnvironmentEmbeddedEditor = class(TComponentEditor) + public + constructor Create(AComponent: TComponent; ADesigner: IDesigner); override; + end; + + TPyEnvironmentEmbeddedPythonVersionProperty = class (TStringProperty) + public + procedure SetValue(const Value: string); override; + end; + +procedure Register(); + +implementation + +uses + ToolsAPI, + PyEnvironment.Project.IDE.Helper, + PyEnvironment.Embeddable; + +procedure Register(); +begin + RegisterComponentEditor(TPyEmbeddedEnvironment, TPyEnvironmentEmbeddedEditor); + RegisterPropertyEditor(TypeInfo(string), TPyEmbeddedEnvironment, 'PythonVersion', TPyEnvironmentEmbeddedPythonVersionProperty); +end; + +{ TPyEnvironmentEmbeddedPythonVersionProperty } + +procedure TPyEnvironmentEmbeddedPythonVersionProperty.SetValue( + const Value: string); +var + LProject: IOTAProject; +begin + LProject := GetActiveProject(); + if Assigned(LProject) and TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[LProject] then begin + inherited SetValue(TPyEnvironmentProjectHelper.CurrentPythonVersion[LProject]) + end else + inherited; +end; + +{ TPyEnvironmentEmbeddedEditor } + +constructor TPyEnvironmentEmbeddedEditor.Create(AComponent: TComponent; + ADesigner: IDesigner); +var + LProject: IOTAProject; + LEnvironment: TPyEmbeddedEnvironment; + LUpdateNeeded: Boolean; +begin + inherited; + LProject := GetActiveProject(); + if TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[LProject] then begin + LEnvironment := (Self.Component as TPyEmbeddedEnvironment); + LUpdateNeeded := false; + if (LEnvironment.PythonVersion <> TPyEnvironmentProjectHelper.CurrentPythonVersion[LProject]) + or LEnvironment.Scanner.AutoScan = false + or (LEnvironment.Scanner.ScanRule <> TPyEmbeddedEnvironment.TScanRule.srFileName) then + LUpdateNeeded := true; + + LEnvironment.PythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[LProject]; + LEnvironment.Scanner.AutoScan := true; + LEnvironment.Scanner.ScanRule := TPyEmbeddedEnvironment.TScanRule.srFileName; + + if LUpdateNeeded then + Designer.Modified(); + end; +end; + +end. diff --git a/src/PyEnvironment.Distribution.pas b/src/PyEnvironment.Distribution.pas index d029f02..8b390fa 100644 --- a/src/PyEnvironment.Distribution.pas +++ b/src/PyEnvironment.Distribution.pas @@ -1,98 +1,106 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Distribution' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment distribution *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Distribution; - -interface - -uses - System.Classes, - System.SysUtils, - System.SysConst, - PyTools.Cancelation; - -type - TPyDistribution = class abstract(TCollectionItem) - private - FPythonVersion: string; - FHome: string; - FSharedLibrary: string; - FExecutable: string; - public - function Setup(const ACancelation: ICancelation): boolean; virtual; - function IsAvailable(): boolean; virtual; - published - property PythonVersion: string read FPythonVersion write FPythonVersion; - property Home: string read FHome write FHome; - property SharedLibrary: string read FSharedLibrary write FSharedLibrary; - property Executable: string read FExecutable write FExecutable; - end; - - TPyDistributionCollection = class abstract(TOwnedCollection) - public - function LocateEnvironment(APythonVersion: string): TPyDistribution; virtual; - end; - -implementation - -uses - System.IOUtils; - -{ TPyDistribution } - -function TPyDistribution.IsAvailable: boolean; -begin - Result := not FPythonVersion.IsEmpty() - and TDirectory.Exists(FHome) - and TFile.Exists(FSharedLibrary) - and TFile.Exists(FExecutable) -end; - -function TPyDistribution.Setup(const ACancelation: ICancelation): boolean; -begin - ACancelation.CheckCancelled(); - Result := false; -end; - -{ TPyDistributionCollection } - -function TPyDistributionCollection.LocateEnvironment( - APythonVersion: string): TPyDistribution; -var - I: Integer; -begin - for I := 0 to Count - 1 do begin - if (TPyDistribution(Items[I]).PythonVersion = APythonVersion) then - Exit(TPyDistribution(Items[I])); - end; - Result := nil; -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Distribution' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment distribution *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Distribution; + +interface + +uses + System.Classes, + System.SysUtils, + System.SysConst, + PyTools.Cancelation; + +type + TPyDistribution = class abstract(TCollectionItem) + private + FPythonVersion: string; + FHome: string; + FPath: string; + FSharedLibrary: string; + FExecutable: string; + public + function Setup(const ACancelation: ICancelation): boolean; virtual; + function IsAvailable(): boolean; virtual; + published + property PythonVersion: string read FPythonVersion write FPythonVersion; + property Home: string read FHome write FHome; + property Path: string read FPath write FPath; + property SharedLibrary: string read FSharedLibrary write FSharedLibrary; + property Executable: string read FExecutable write FExecutable; + end; + + TPyDistributionCollection = class abstract(TOwnedCollection) + public + function LocateEnvironment(APythonVersion: string): TPyDistribution; virtual; + end; + +implementation + +uses + System.IOUtils, + PyEnvironment.Path; + +{ TPyDistribution } + +function TPyDistribution.IsAvailable: boolean; +begin + Result := not FPythonVersion.IsEmpty() + and TDirectory.Exists(TPyEnvironmentPath.ResolvePath(FHome)) + and TFile.Exists(TPyEnvironmentPath.ResolvePath(FSharedLibrary)) + {$IFNDEF IOS64} + and TFile.Exists(TPyEnvironmentPath.ResolvePath(FExecutable)) + {$ELSE} + and TDirectory.Exists(TPyEnvironmentPath.ResolvePath(FPath)) + {$ENDIF} + ; +end; + +function TPyDistribution.Setup(const ACancelation: ICancelation): boolean; +begin + ACancelation.CheckCancelled(); + Result := false; +end; + +{ TPyDistributionCollection } + +function TPyDistributionCollection.LocateEnvironment( + APythonVersion: string): TPyDistribution; +var + I: Integer; +begin + for I := 0 to Count - 1 do begin + if (TPyDistribution(Items[I]).PythonVersion = APythonVersion) then + Exit(TPyDistribution(Items[I])); + end; + Result := nil; +end; + +end. diff --git a/src/PyEnvironment.Exception.pas b/src/PyEnvironment.Exception.pas index c218131..9b061d3 100644 --- a/src/PyEnvironment.Exception.pas +++ b/src/PyEnvironment.Exception.pas @@ -1,23 +1,23 @@ -unit PyEnvironment.Exception; - -interface - -uses - System.SysUtils; - -type - EInvalidFileStructure = class(Exception); - - EEmbeddableNotFound = class(Exception); - - EPipSetupFailed = class(Exception); - - EFileNotFoundException = System.SysUtils.EFileNotFoundException; - - EDirectoryNotFoundException = System.SysUtils.EDirectoryNotFoundException; - - ESymlinkFailed = class(Exception); - -implementation - -end. +unit PyEnvironment.Exception; + +interface + +uses + System.SysUtils; + +type + EInvalidFileStructure = class(Exception); + + EEmbeddableNotFound = class(Exception); + + EPipSetupFailed = class(Exception); + + EFileNotFoundException = System.SysUtils.EFileNotFoundException; + + EDirectoryNotFoundException = System.SysUtils.EDirectoryNotFoundException; + + ESymlinkFailed = class(Exception); + +implementation + +end. diff --git a/src/PyEnvironment.Local.pas b/src/PyEnvironment.Local.pas index 9cc425c..bce1b72 100644 --- a/src/PyEnvironment.Local.pas +++ b/src/PyEnvironment.Local.pas @@ -1,157 +1,157 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Local' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment Local *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Local; - -interface - -uses - System.Classes, - System.SysUtils, - System.JSON, - PyTools.Cancelation, - PyEnvironment, - PyEnvironment.Distribution; - -type - (*-----------------------------------------------------------------------*) - (* *) - (* JSON structure example *) - (* *) - (* JSON *) - (* [{"python_version": *) - (* {"home": "", *) - (* "shared_library": "", *) - (* "executable": ""}}] *) - (*-----------------------------------------------------------------------*) - TPyLocalDistribution = class(TPyDistribution) - public - function Setup(const ACancelation: ICancelation): boolean; override; - end; - - TPyLocalCollection = class(TPyDistributionCollection); - - [ComponentPlatforms(pidAllPlatforms)] - TPyLocalEnvironment = class(TPyEnvironment) - private - FFilePath: string; - procedure EnumerateEnvironments(const AProc: TProc); - protected - function CreateCollection(): TPyDistributionCollection; override; - procedure Prepare(const ACancelation: ICancelation); override; - published - property Distributions; - property FilePath: string read FFilePath write FFilePath; - end; - -implementation - -uses - System.IOUtils, - PythonEngine, - PyEnvironment.Exception, - PyEnvironment.Path; - -{ TPyLocalDistribution } - -function TPyLocalDistribution.Setup(const ACancelation: ICancelation): boolean; -begin - inherited; - Result := true; -end; - -{ TPyLocalEnvironment } - -function TPyLocalEnvironment.CreateCollection: TPyDistributionCollection; -begin - Result := TPyLocalCollection.Create(Self, TPyLocalDistribution); -end; - -procedure TPyLocalEnvironment.EnumerateEnvironments(const AProc: TProc); -var - LPythonVersions: TJSONValue; - I: Integer; - LPythonVersion: TJSONValue; - LDistribution: TJSONValue; - LFilePath: string; -begin - LFilePath := TPyEnvironmentPath.ResolvePath(FFilePath); - LPythonVersions := TJSONObject.ParseJSONValue(TFile.ReadAllText(LFilePath)); - try - if not (Assigned(LPythonVersions) and (LPythonVersions is TJSONArray)) then - raise EInvalidFileStructure.Create('Invalid file structure.'); - - for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin - for LPythonVersion in TJSONArray(LPythonVersions) do begin - if not (LPythonVersion is TJSONObject) then - raise EInvalidFileStructure.Create('Invalid file structure.'); - - LDistribution := TJSONObject(LPythonVersion).Values[PYTHON_KNOWN_VERSIONS[I].RegVersion]; - - if not Assigned(LDistribution) then - Continue; - - if not (LDistribution is TJSONObject) then - raise EInvalidFileStructure.Create('Invalid file structure.'); - - AProc(PYTHON_KNOWN_VERSIONS[I].RegVersion, TJSONObject(LDistribution)); - end; - end; - finally - LPythonVersions.Free(); - end; -end; - -procedure TPyLocalEnvironment.Prepare(const ACancelation: ICancelation); -var - LFilePath: string; -begin - LFilePath := TPyEnvironmentPath.ResolvePath(FFilePath); - if not TFile.Exists(LFilePath) then - raise EFileNotFoundException.CreateFmt('File not found.' + #13#10 + '%s', [LFilePath]); - - EnumerateEnvironments( - procedure(APythonVersion: string; AEnvironmentInfo: TJSONObject) - var - LDistribution: TPyLocalDistribution; - begin - ACancelation.CheckCancelled(); - - LDistribution := TPyLocalDistribution(Distributions.Add()); - LDistribution.PythonVersion := APythonVersion; - LDistribution.Home := AEnvironmentInfo.GetValue('home'); - LDistribution.SharedLibrary := AEnvironmentInfo.GetValue('shared_library'); - LDistribution.Executable := AEnvironmentInfo.GetValue('executable'); - end); - - inherited; -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Local' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment Local *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Local; + +interface + +uses + System.Classes, + System.SysUtils, + System.JSON, + PyTools.Cancelation, + PyEnvironment, + PyEnvironment.Distribution; + +type + (*-----------------------------------------------------------------------*) + (* *) + (* JSON structure example *) + (* *) + (* JSON *) + (* [{"python_version": *) + (* {"home": "", *) + (* "shared_library": "", *) + (* "executable": ""}}] *) + (*-----------------------------------------------------------------------*) + TPyLocalDistribution = class(TPyDistribution) + public + function Setup(const ACancelation: ICancelation): boolean; override; + end; + + TPyLocalCollection = class(TPyDistributionCollection); + + [ComponentPlatforms(pidAllPlatforms)] + TPyLocalEnvironment = class(TPyEnvironment) + private + FFilePath: string; + procedure EnumerateEnvironments(const AProc: TProc); + protected + function CreateCollection(): TPyDistributionCollection; override; + procedure Prepare(const ACancelation: ICancelation); override; + published + property Distributions; + property FilePath: string read FFilePath write FFilePath; + end; + +implementation + +uses + System.IOUtils, + PythonEngine, + PyEnvironment.Exception, + PyEnvironment.Path; + +{ TPyLocalDistribution } + +function TPyLocalDistribution.Setup(const ACancelation: ICancelation): boolean; +begin + inherited; + Result := true; +end; + +{ TPyLocalEnvironment } + +function TPyLocalEnvironment.CreateCollection: TPyDistributionCollection; +begin + Result := TPyLocalCollection.Create(Self, TPyLocalDistribution); +end; + +procedure TPyLocalEnvironment.EnumerateEnvironments(const AProc: TProc); +var + LPythonVersions: TJSONValue; + I: Integer; + LPythonVersion: TJSONValue; + LDistribution: TJSONValue; + LFilePath: string; +begin + LFilePath := TPyEnvironmentPath.ResolvePath(FFilePath); + LPythonVersions := TJSONObject.ParseJSONValue(TFile.ReadAllText(LFilePath)); + try + if not (Assigned(LPythonVersions) and (LPythonVersions is TJSONArray)) then + raise EInvalidFileStructure.Create('Invalid file structure.'); + + for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin + for LPythonVersion in TJSONArray(LPythonVersions) do begin + if not (LPythonVersion is TJSONObject) then + raise EInvalidFileStructure.Create('Invalid file structure.'); + + LDistribution := TJSONObject(LPythonVersion).Values[PYTHON_KNOWN_VERSIONS[I].RegVersion]; + + if not Assigned(LDistribution) then + Continue; + + if not (LDistribution is TJSONObject) then + raise EInvalidFileStructure.Create('Invalid file structure.'); + + AProc(PYTHON_KNOWN_VERSIONS[I].RegVersion, TJSONObject(LDistribution)); + end; + end; + finally + LPythonVersions.Free(); + end; +end; + +procedure TPyLocalEnvironment.Prepare(const ACancelation: ICancelation); +var + LFilePath: string; +begin + LFilePath := TPyEnvironmentPath.ResolvePath(FFilePath); + if not TFile.Exists(LFilePath) then + raise EFileNotFoundException.CreateFmt('File not found.' + #13#10 + '%s', [LFilePath]); + + EnumerateEnvironments( + procedure(APythonVersion: string; AEnvironmentInfo: TJSONObject) + var + LDistribution: TPyLocalDistribution; + begin + ACancelation.CheckCancelled(); + + LDistribution := TPyLocalDistribution(Distributions.Add()); + LDistribution.PythonVersion := APythonVersion; + LDistribution.Home := AEnvironmentInfo.GetValue('home'); + LDistribution.SharedLibrary := AEnvironmentInfo.GetValue('shared_library'); + LDistribution.Executable := AEnvironmentInfo.GetValue('executable'); + end); + + inherited; +end; + +end. diff --git a/src/PyEnvironment.Path.pas b/src/PyEnvironment.Path.pas index cd91f20..d5ccf07 100644 --- a/src/PyEnvironment.Path.pas +++ b/src/PyEnvironment.Path.pas @@ -1,120 +1,124 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Path' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment paths resolution *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Path; - -interface - -type - TPyEnvironmentPath = class - public const - ENVIRONMENT_PATH = '$(ENVIRONMENT_PATH)'; - EMBEDDABLES_PATH = '$(EMBEDDABLES_PATH)'; - PYTHON_VER = '$(PYTHON_VER)'; - public - class function CreateEmbeddablesPath(const APythonVer: boolean = false): string; - class function CreateEnvironmentPath(const APythonVer: boolean = true): string; - /// - /// This function might resolve path variables, relative paths and whatever regarding paths - /// - class function ResolvePath(const APath: string): string; overload; static; - class function ResolvePath(const APath, APythonVersion: string): string; overload; static; - end; - -implementation - -uses - System.IOUtils, System.SysUtils, System.RegularExpressions; - -{ TPyEnvironmentPath } - -class function TPyEnvironmentPath.CreateEmbeddablesPath( - const APythonVer: boolean): string; -begin - Result := EMBEDDABLES_PATH; - if APythonVer then - Result := TPath.Combine(Result, PYTHON_VER); -end; - -class function TPyEnvironmentPath.CreateEnvironmentPath( - const APythonVer: boolean): string; -begin - Result := ENVIRONMENT_PATH; - if APythonVer then - Result := TPath.Combine(Result, PYTHON_VER); -end; - -class function TPyEnvironmentPath.ResolvePath(const APath: string): string; - - function GetRootPath(): string; - begin - {$IFDEF ANDROID} - Result := TPath.GetDocumentsPath(); - {$ELSE} - Result := TPath.GetDirectoryName(GetModuleName(HInstance)); - {$ENDIF} - end; - -begin - //Fix \ or / as dir separator. It varies in different platforms - Result := TRegEx.Replace(APath, - '[' - + TRegEx.Escape('\') - + TRegEx.Escape('/') - + ']', - TPath.DirectorySeparatorChar); - //Replace the DEPLOY_PATH variable with the app root path - Result := Result.Replace(ENVIRONMENT_PATH, GetRootPath()); - //Replace the EMBEDDABLES_PATH variable with the platform specific path - {$IFDEF ANDROID} - Result := Result.Replace(EMBEDDABLES_PATH, TPath.GetDocumentsPath()); - {$ELSEIF DEFINED(MACOS)} - Result := Result.Replace( - EMBEDDABLES_PATH, - TPath.Combine( - TDirectory.GetParent(TPath.GetDirectoryName(GetModuleName(HInstance))), - 'Resources')); - {$ELSE} - Result := Result.Replace(EMBEDDABLES_PATH, GetRootPath()); - {$ENDIF} - - //Relative path, maybe!? - if (Result <> ExpandFileName(Result)) then - Result := TPath.Combine(GetRootPath(), Result); -end; - -class function TPyEnvironmentPath.ResolvePath(const APath, - APythonVersion: string): string; -begin - Result := APath.Replace(PYTHON_VER, APythonVersion); - Result := ResolvePath(Result); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Path' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment paths resolution *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Path; + +interface + +type + TPyEnvironmentPath = class + public const + ENVIRONMENT_PATH = '$(ENVIRONMENT_PATH)'; + EMBEDDABLES_PATH = '$(EMBEDDABLES_PATH)'; + PYTHON_VER = '$(PYTHON_VER)'; + public + class function CreateEmbeddablesPath(const APythonVer: boolean = false): string; + class function CreateEnvironmentPath(const APythonVer: boolean = true): string; + /// + /// This function might resolve path variables, relative paths and whatever regarding paths + /// + class function ResolvePath(const APath: string): string; overload; static; + class function ResolvePath(const APath, APythonVersion: string): string; overload; static; + end; + +implementation + +uses + System.IOUtils, System.SysUtils, System.RegularExpressions; + +{ TPyEnvironmentPath } + +class function TPyEnvironmentPath.CreateEmbeddablesPath( + const APythonVer: boolean): string; +begin + Result := EMBEDDABLES_PATH; + if APythonVer then + Result := TPath.Combine(Result, PYTHON_VER); +end; + +class function TPyEnvironmentPath.CreateEnvironmentPath( + const APythonVer: boolean): string; +begin + Result := ENVIRONMENT_PATH; + if APythonVer then + Result := TPath.Combine(Result, PYTHON_VER); +end; + +class function TPyEnvironmentPath.ResolvePath(const APath: string): string; + + function GetRootPath(): string; + begin + {$IFDEF ANDROID} + Result := TPath.GetDocumentsPath(); + {$ELSEIF DEFINED(IOS64)} + Result := TPath.GetAppPath(); + {$ELSEIF DEFINED(OSX)} + Result := TPath.Combine( + TDirectory.GetParent(TPath.GetDirectoryName(GetModuleName(HInstance))), + 'Resources'); + {$ELSE} + Result := TPath.GetDirectoryName(GetModuleName(HInstance)); + {$IFEND} + end; + +begin + //Fix \ or / as dir separator. It varies in different platforms + Result := TRegEx.Replace(APath, + '[' + + TRegEx.Escape('\') + + TRegEx.Escape('/') + + ']', + TPath.DirectorySeparatorChar); + //Replace the DEPLOY_PATH variable with the app root path + Result := Result.Replace(ENVIRONMENT_PATH, GetRootPath()); + //Replace the EMBEDDABLES_PATH variable with the platform specific path + {$IFDEF ANDROID} + Result := Result.Replace(EMBEDDABLES_PATH, TPath.GetDocumentsPath()); + {$ELSEIF DEFINED(IOS64)} + Result := Result.Replace(EMBEDDABLES_PATH, GetRootPath()); + {$ELSEIF DEFINED(MACOS)} + Result := Result.Replace(EMBEDDABLES_PATH, GetRootPath()); + {$ELSE} + Result := Result.Replace(EMBEDDABLES_PATH, GetRootPath()); + {$ENDIF} + + //Relative path, maybe!? + if (Result <> ExpandFileName(Result)) then + Result := TPath.Combine(GetRootPath(), Result); +end; + +class function TPyEnvironmentPath.ResolvePath(const APath, + APythonVersion: string): string; +begin + Result := APath.Replace(PYTHON_VER, APythonVersion); + Result := ResolvePath(Result); +end; + +end. diff --git a/src/PyEnvironment.Registration.pas b/src/PyEnvironment.Registration.pas index ea4481b..a6859c9 100644 --- a/src/PyEnvironment.Registration.pas +++ b/src/PyEnvironment.Registration.pas @@ -1,76 +1,74 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment.Registration' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironments components registration *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment.Registration; - -interface - -procedure Register(); - -implementation - -uses - Classes, - PyEnvironment, - //Embed Python - PyEnvironment.Embeddable, - //Embed Python as a resource - PyEnvironment.Embeddable.Res.Python37, - PyEnvironment.Embeddable.Res.Python38, - PyEnvironment.Embeddable.Res.Python39, - PyEnvironment.Embeddable.Res.Python310, - //Local Python installations - PyEnvironment.Local, - //Environment add-ons - PyEnvironment.AddOn, - //PIP add-ons - PyEnvironment.AddOn.GetPip, - PyEnvironment.AddOn.EnsurePip; - -procedure Register(); -begin - RegisterComponents('Python - Environments', [ - //Embed Python - TPyEmbeddedEnvironment, - //Embed Python as a resource - TPyEmbeddedResEnvironment37, - TPyEmbeddedResEnvironment38, - TPyEmbeddedResEnvironment39, - TPyEmbeddedResEnvironment310, - //Local Python installations - TPyLocalEnvironment, - //Environment add-ons - TPyEnvironmentAddOn, - //PIP add-ons - TPyEnvironmentAddOnGetPip, - TPyEnvironmentAddOnEnsurePip]); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment.Registration' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironments components registration *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment.Registration; + +interface + +procedure Register(); + +implementation + +uses + Classes, + PyEnvironment, + //Embed Python + PyEnvironment.Embeddable, + //Embed Python as a resource + PyEnvironment.Embeddable.Res.Python38, + PyEnvironment.Embeddable.Res.Python39, + PyEnvironment.Embeddable.Res.Python310, + //Local Python installations + PyEnvironment.Local, + //Environment add-ons + PyEnvironment.AddOn, + //PIP add-ons + PyEnvironment.AddOn.GetPip, + PyEnvironment.AddOn.EnsurePip; + +procedure Register(); +begin + RegisterComponents('Python - Environments', [ + //Embed Python + TPyEmbeddedEnvironment, + //Embed Python as a resource + TPyEmbeddedResEnvironment38, + TPyEmbeddedResEnvironment39, + TPyEmbeddedResEnvironment310, + //Local Python installations + TPyLocalEnvironment, + //Environment add-ons + TPyEnvironmentAddOn, + //PIP add-ons + TPyEnvironmentAddOnGetPip, + TPyEnvironmentAddOnEnsurePip]); +end; + +end. diff --git a/src/PyEnvironment.pas b/src/PyEnvironment.pas index 3e620fe..f17c2ee 100644 --- a/src/PyEnvironment.pas +++ b/src/PyEnvironment.pas @@ -1,887 +1,891 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyEnvironment' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: PyEnvironment layer *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyEnvironment; - -interface - -uses - System.Classes, - System.Rtti, - System.SysUtils, - System.Threading, - System.Generics.Collections, - System.Types, - System.SysConst, - PythonEngine, - PyTools.Cancelation, - PyEnvironment.Distribution; - -type - {$SCOPEDENUMS ON} - TPyPluginEvent = ( - BeforeSetup, AfterSetup, - BeforeActivate, AfterActivate, - BeforeDeactivate, AfterDeactivate); - {$SCOPEDENUMS OFF} - TPyPluginEvents = set of TPyPluginEvent; - - TPyPluginInfo = record - public - Name: string; - Description: string; - - InstallsWhen: TPyPluginEvents; - UninstallsWhen: TPyPluginEvents; - LoadsWhen: TPyPluginEvents; - UnloadsWhen: TPyPluginEvents; - end; - - TPyEnvironmentBeforeSetup = procedure(Sender: TObject; const APythonVersion: string) of object; - TPyEnvironmentAfterSetup = procedure(Sender: TObject; const APythonVersion: string) of object; - TPyEnvironmentBeforeActivate = procedure(Sender: TObject; const APythonVersion: string) of object; - TPyEnvironmentAfterActivate = procedure(Sender: TObject; const APythonVersion: string; const AActivated: boolean) of object; - TPyEnvironmentBeforeDeactivate = procedure(Sender: TObject; const APythonVersion: string) of object; - TPyEnvironmentAfterDeactivate = procedure(Sender: TObject; const APythonVersion: string) of object; - TPyEnvironmentReady = procedure(Sender: TObject; const APythonVersion: string) of object; - TPyEnvironmentError = procedure(Sender: TObject; const AException: Exception) of object; - //Plugins - TPyEnvironmentPluginInstall = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; - TPyEnvironmentPluginUninstall = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; - TPyEnvironmentPluginLoad = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; - TPyEnvironmentPluginUnload = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; - - IPyEnvironmentPlugin = interface - ['{11906029-81A6-4F94-ACDA-2DBB346C6E3A}'] - function GetInfo(): TPyPluginInfo; - - procedure InstallPlugin(const ACancelation: ICancelation); - procedure UninstallPlugin(const ACancelation: ICancelation); - - procedure LoadPlugin(const ACancelation: ICancelation); - procedure UnloadPlugin(const ACancelation: ICancelation); - - function IsInstalled(): boolean; - - property Info: TPyPluginInfo read GetInfo; - end; - - TPyCustomEnvironment = class(TComponent) - protected type - TEnvironmentTaskAsyncResult = class(TBaseAsyncResult) - private - FAsyncTask: TProc; - FAsyncCallback: TAsyncCallback; - FCancelation: ICancelation; - protected - procedure Complete; override; - procedure Schedule; override; - procedure AsyncDispatch; override; - function DoCancel: Boolean; override; - public - constructor Create(const AContext: TObject; - const AAsyncTask: TProc; - const AAsyncCallback: TAsyncCallback = nil); - end; - - TAsyncFuncCallback = reference to procedure ( - const ASyncResult: IAsyncResult; const AResult: TResult); - - TEnvironmentTaskAsyncResult = class(TEnvironmentTaskAsyncResult) - private - FRetVal: TResult; - public - constructor Create(const AContext: TObject; const AAsyncTask: TFunc; - const AAsyncFuncCallback: TAsyncFuncCallback = nil); - function GetRetVal: TResult; - end; - - TAsyncSetup = class sealed(TEnvironmentTaskAsyncResult); - - TAsyncActivate = class sealed(TEnvironmentTaskAsyncResult) - private - FSetupResult: IAsyncResult; - protected - procedure AsyncDispatch; override; - public - constructor Create(const AContext: TObject; - const ASetupResult: IAsyncResult; - const AAsyncTask: TFunc; - const AAsyncFuncCallback: TAsyncFuncCallback = nil); - end; - private - FDistributions: TPyDistributionCollection; - FPlugins: TList; - FAutoLoad: boolean; - FPythonEngine: TPythonEngine; - FPythonVersion: string; - //Async options - FSynchronizeEvents: boolean; - //Events - FBeforeSetup: TPyEnvironmentBeforeSetup; - FAfterSetup: TPyEnvironmentAfterSetup; - FBeforeActivate: TPyEnvironmentBeforeActivate; - FAfterActivate: TPyEnvironmentAfterActivate; - FBeforeDeactivate: TPyEnvironmentBeforeDeactivate; - FAfterDeactivate: TPyEnvironmentAfterDeactivate; - FOnReady: TPyEnvironmentReady; - FOnError: TPyEnvironmentError; - //Plugin events - FOnPluginInstall: TPyEnvironmentPluginInstall; - FOnPluginUninstall: TPyEnvironmentPluginUninstall; - FOnPluginLoad: TPyEnvironmentPluginLoad; - FOnPluginUnload: TPyEnvironmentPluginUnload; - procedure SetDistributions(const ADistributions: TPyDistributionCollection); - procedure SetPythonEngine(const APythonEngine: TPythonEngine); - procedure DoAutoLoad; - //Hooked routines - function InternalSetup(const APythonVersion: string; - const ACancelation: ICancelation): boolean; - function InternalActivate(const APythonVersion: string; - const ACancelation: ICancelation): boolean; - procedure InternalDeactivate(const ACancelation: ICancelation); - //Event handlers - procedure DoBeforeSetup(APythonVersion: string; const ACancelation: ICancelation); - procedure DoAfterSetup(APythonVersion: string; - const ADistribution: TPyDistribution; const ACancelation: ICancelation); - procedure DoBeforeActivate(APythonVersion: string; const ACancelation: ICancelation); - procedure DoAfterActivate( - APythonVersion: string; const ADistribution: TPyDistribution; - var Result: Boolean; const ACancelation: ICancelation); - procedure DoBeforeDeactivate(const ACancelation: ICancelation); - procedure DoAfterDeactivate(const ACancelation: ICancelation); - procedure DoPluginInstall(const APlugin: TObject; const AInfo: TPyPluginInfo); - procedure DoPluginUninstall(const APlugin: TObject; const AInfo: TPyPluginInfo); - procedure DoPluginLoad(const APlugin: TObject; const AInfo: TPyPluginInfo); - procedure DoPluginUnload(const APlugin: TObject; const AInfo: TPyPluginInfo); - //It won't be ready until setup, activate, install and load plugins - procedure DoInternalReady; - procedure DoInternalError; - protected - procedure Loaded(); override; - procedure Notification(AComponent: TComponent; AOperation: TOperation); override; - procedure HandleEvent(const ASynchronizeEvents: boolean; - const AThreadProc: TThreadProcedure); - //Plugins install/uninstall/load/unload - procedure InstallAndLoadPlugins(const AEvent: TPyPluginEvent; - const ACancelation: ICancelation); virtual; - procedure UninstallAndUnloadPlugins(const AEvent: TPyPluginEvent; - const ACancelation: ICancelation); virtual; - protected - procedure SetPythonVersion(const Value: string); virtual; - function CreateCollection(): TPyDistributionCollection; virtual; abstract; - procedure Prepare(const ACancelation: ICancelation); virtual; - public - constructor Create(AOwner: TComponent); override; - destructor Destroy(); override; - /// - /// Setup an environment. - /// - function Setup(APythonVersion: string = ''): boolean; - /// - /// Activate an environment. - /// - function Activate(APythonVersion: string = ''): boolean; - /// - /// Deactivate an environment. - /// - procedure Deactivate(); - - function SetupAsync(const APythonVersion: string; - const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; - function SetupAsync( - const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; - function ActivateAsync(const APythonVersion: string; - const ASetupAsync: IAsyncResult; - const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; - function ActivateAsync(const ASetupAsync: IAsyncResult; - const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; - function ActivateAsync( - const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; - /// - /// Async begin setup an environment. - /// - function BeginSetup(const APythonVersion: string = ''): IAsyncResult; - /// - /// Wait for the async setup action to complete. - /// - function EndSetup(const ASyncResult: IAsyncResult): boolean; - /// - /// Queue the activate action to the main thread. - /// - function BeginActivate(const APythonVersion: string = ''): IAsyncResult; - /// - /// Wait for the activate action to complete. - /// Never call this routine directly from main thread. - /// - function EndActivate(const ASyncResult: IAsyncResult): boolean; - //Plugins - procedure AddPlugin(const APlugin: IPyEnvironmentPlugin); - procedure RemovePlugin(const APlugin: IPyEnvironmentPlugin); - public - property Distributions: TPyDistributionCollection read FDistributions write SetDistributions; - property AutoLoad: boolean read FAutoLoad write FAutoLoad; - property PythonVersion: string read FPythonVersion write SetPythonVersion; - property PythonEngine: TPythonEngine read FPythonEngine write SetPythonEngine; - /// - /// Automatically sync events when running async. - /// - property SynchronizeEvents: boolean read FSynchronizeEvents write FSynchronizeEvents default true; - published - property BeforeSetup: TPyEnvironmentBeforeSetup read FBeforeSetup write FBeforeSetup; - property AfterSetup: TPyEnvironmentAfterSetup read FAfterSetup write FAfterSetup; - property BeforeActivate: TPyEnvironmentBeforeActivate read FBeforeActivate write FBeforeActivate; - property AfterActivate: TPyEnvironmentAfterActivate read FAfterActivate write FAfterActivate; - property BeforeDeactivate: TPyEnvironmentBeforeDeactivate read FBeforeDeactivate write FBeforeDeactivate; - property AfterDeactivate: TPyEnvironmentAfterDeactivate read FAfterDeactivate write FAfterDeactivate; - property OnError: TPyEnvironmentError read FOnError write FOnError; - property OnReady: TPyEnvironmentReady read FOnReady write FOnReady; - //Plugin events - property OnPluginInstall: TPyEnvironmentPluginInstall read FOnPluginInstall write FOnPluginInstall; - property OnPluginUninstall: TPyEnvironmentPluginUninstall read FOnPluginUninstall write FOnPluginUninstall; - property OnPluginLoad: TPyEnvironmentPluginLoad read FOnPluginLoad write FOnPluginLoad; - property OnPluginUnload: TPyEnvironmentPluginUnload read FOnPluginUnload write FOnPluginUnload; - end; - - TPyEnvironment = class(TPyCustomEnvironment) - private - protected - procedure DefineProperties(Filer: TFiler); override; - procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override; - function GetChildOwner: TComponent; override; - published - property AutoLoad; - property SynchronizeEvents; - property PythonVersion; - property PythonEngine; - end; - -implementation - -uses - System.IOUtils, - System.StrUtils, - TypInfo, - PyEnvironment.Path; - -{ TPyCustomEnvironment } - -constructor TPyCustomEnvironment.Create(AOwner: TComponent); -begin - inherited; - FDistributions := CreateCollection(); - FPlugins := TList.Create(); - FSynchronizeEvents := true; -end; - -destructor TPyCustomEnvironment.Destroy; -begin - FPlugins.Free(); - FDistributions.Free(); - SetPythonEngine(nil); - inherited; -end; - -procedure TPyCustomEnvironment.Loaded; -begin - inherited; - DoAutoLoad(); -end; - -procedure TPyCustomEnvironment.Notification(AComponent: TComponent; - AOperation: TOperation); -begin - inherited; - if (AOperation = opRemove) then begin - if (AComponent = FPythonEngine) then - FPythonEngine := nil; - end; -end; - -procedure TPyCustomEnvironment.SetDistributions( - const ADistributions: TPyDistributionCollection); -begin - FDistributions.Assign(ADistributions); -end; - -procedure TPyCustomEnvironment.SetPythonEngine(const APythonEngine: TPythonEngine); -begin - if (APythonEngine <> FPythonEngine) then begin - if Assigned(FPythonEngine) then - FPythonEngine.RemoveFreeNotification(Self); - FPythonEngine := APythonEngine; - if Assigned(FPythonEngine) then begin - FPythonEngine.FreeNotification(Self); - if (csDesigning in ComponentState) then - FPythonEngine.AutoLoad := false; - end; - end; -end; - -procedure TPyCustomEnvironment.SetPythonVersion(const Value: string); -begin - FPythonVersion := Value; -end; - -procedure TPyCustomEnvironment.DoAutoLoad; -begin - if not (csDesigning in ComponentState) and FAutoLoad - and not (PythonVersion.IsEmpty) then - if Setup(FPythonVersion) then - Activate(FPythonVersion); -end; - -function TPyCustomEnvironment.Setup(APythonVersion: string): boolean; -var - LCancelation: ICancelation; -begin - LCancelation := TCancelation.Create(); - Result := InternalSetup(APythonVersion, LCancelation); -end; - -function TPyCustomEnvironment.SetupAsync( - const ACallback: TAsyncFuncCallback): IAsyncResult; -begin - Result := SetupAsync(PythonVersion, ACallback); -end; - -function TPyCustomEnvironment.SetupAsync(const APythonVersion: string; - const ACallback: TAsyncFuncCallback): IAsyncResult; -begin - Result := TAsyncSetup.Create(Self, - function(ACancelation: ICancelation): boolean - begin - Result := InternalSetup( - IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion), - ACancelation); - end).Invoke(); -end; - -function TPyCustomEnvironment.BeginSetup(const APythonVersion: string): IAsyncResult; -begin - Result := SetupAsync(APythonVersion); -end; - -function TPyCustomEnvironment.EndSetup(const ASyncResult: IAsyncResult): boolean; -begin - Result := (ASyncResult as TAsyncSetup).GetRetVal(); -end; - -function TPyCustomEnvironment.BeginActivate( - const APythonVersion: string): IAsyncResult; -begin - Result := ActivateAsync(APythonVersion, nil, nil); -end; - -function TPyCustomEnvironment.EndActivate( - const ASyncResult: IAsyncResult): boolean; -begin - Result := TAsyncActivate(ASyncResult).GetRetVal(); -end; - -function TPyCustomEnvironment.Activate(APythonVersion: string): boolean; -var - LCancelation: ICancelation; -begin - LCancelation := TCancelation.Create(); - - Result := InternalActivate( - IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion), - LCancelation); -end; - -function TPyCustomEnvironment.ActivateAsync(const APythonVersion: string; - const ASetupAsync: IAsyncResult; const ACallback: TAsyncFuncCallback): IAsyncResult; -begin - Result := TAsyncActivate.Create(Self, ASetupAsync, - function(ACancelation: ICancelation): boolean - begin - Result := InternalActivate( - IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion), - ACancelation); - end, ACallback).Invoke(); -end; - -function TPyCustomEnvironment.ActivateAsync(const ASetupAsync: IAsyncResult; - const ACallback: TAsyncFuncCallback): IAsyncResult; -begin - Result := ActivateAsync(PythonVersion, ASetupAsync, ACallback); -end; - -function TPyCustomEnvironment.ActivateAsync( - const ACallback: TAsyncFuncCallback): IAsyncResult; -begin - Result := ActivateAsync(nil, ACallback); -end; - -procedure TPyCustomEnvironment.Deactivate; -var - LCancelation: ICancelation; -begin - LCancelation := TCancelation.Create(); - - InternalDeactivate(LCancelation); -end; - -procedure TPyCustomEnvironment.DoBeforeSetup(APythonVersion: string; - const ACancelation: ICancelation); -begin - if Assigned(FBeforeSetup) then - HandleEvent(FSynchronizeEvents, procedure() begin - FBeforeSetup(Self, APythonVersion); - end); -end; - -procedure TPyCustomEnvironment.DoAfterSetup(APythonVersion: string; - const ADistribution: TPyDistribution; const ACancelation: ICancelation); -begin - if Assigned(FAfterSetup) then - HandleEvent(FSynchronizeEvents, procedure() begin - FAfterSetup(Self, APythonVersion); - end); -end; - -procedure TPyCustomEnvironment.DoBeforeActivate(APythonVersion: string; - const ACancelation: ICancelation); -begin - if Assigned(FBeforeActivate) then - HandleEvent(FSynchronizeEvents, procedure() begin - FBeforeActivate(Self, APythonVersion); - end); -end; - -procedure TPyCustomEnvironment.DoAfterActivate(APythonVersion: string; - const ADistribution: TPyDistribution; var Result: Boolean; - const ACancelation: ICancelation); -var - LResult: Boolean; -begin - LResult := Result; - if Assigned(FAfterActivate) then - HandleEvent(FSynchronizeEvents, procedure() begin - FAfterActivate(Self, APythonVersion, LResult); - end); - Result := LResult; -end; - -procedure TPyCustomEnvironment.DoBeforeDeactivate( - const ACancelation: ICancelation); -begin - if Assigned(FBeforeDeactivate) then - HandleEvent(FSynchronizeEvents, procedure() begin - FBeforeDeactivate(Self, FPythonEngine.RegVersion); - end); -end; - -procedure TPyCustomEnvironment.DoAfterDeactivate( - const ACancelation: ICancelation); -begin - if Assigned(FAfterDeactivate) then - HandleEvent(FSynchronizeEvents, procedure() begin - FAfterDeactivate(Self, FPythonEngine.RegVersion); - end); -end; - -procedure TPyCustomEnvironment.DoPluginInstall(const APlugin: TObject; - const AInfo: TPyPluginInfo); -begin - if Assigned(FOnPluginInstall) then - HandleEvent(FSynchronizeEvents, procedure() begin - FOnPluginInstall(APlugin, AInfo); - end); -end; - -procedure TPyCustomEnvironment.DoPluginUninstall(const APlugin: TObject; - const AInfo: TPyPluginInfo); -begin - if Assigned(FOnPluginUninstall) then - HandleEvent(FSynchronizeEvents, procedure() begin - FOnPluginUninstall(APlugin, AInfo); - end); -end; - -procedure TPyCustomEnvironment.DoPluginLoad(const APlugin: TObject; - const AInfo: TPyPluginInfo); -begin - if Assigned(FOnPluginLoad) then - HandleEvent(FSynchronizeEvents, procedure() begin - FOnPluginLoad(APlugin, AInfo); - end); -end; - -procedure TPyCustomEnvironment.DoPluginUnload(const APlugin: TObject; - const AInfo: TPyPluginInfo); -begin - if Assigned(FOnPluginUnload) then - HandleEvent(FSynchronizeEvents, procedure() begin - FOnPluginUnload(APlugin, AInfo); - end); -end; - -procedure TPyCustomEnvironment.DoInternalReady; -begin - if Assigned(FOnReady) then - TThread.Synchronize(nil, procedure() begin - FOnReady(Self, FPythonEngine.RegVersion); - end); -end; - -procedure TPyCustomEnvironment.DoInternalError; -begin - if Assigned(FOnError) then begin - HandleEvent(FSynchronizeEvents, - procedure() - var - LException: Exception; - begin - LException := Exception(ExceptObject()); - FOnError(Self, LException); - end); - - Abort(); - end else begin - try - raise Exception(AcquireExceptionObject()) at ExceptAddr(); - finally - ReleaseExceptionObject(); - end; - end; -end; - -procedure TPyCustomEnvironment.HandleEvent(const ASynchronizeEvents: boolean; - const AThreadProc: TThreadProcedure); -begin - if ASynchronizeEvents and Assigned(WakeMainThread) and - (MainThreadID <> TThread.Current.ThreadID) then - TThread.Synchronize(TThread.Current, AThreadProc) - else - AThreadProc(); -end; - -function TPyCustomEnvironment.InternalSetup(const APythonVersion: string; - const ACancelation: ICancelation): boolean; -var - LDistribution: TPyDistribution; -begin - Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); - - LDistribution := nil; - - DoBeforeSetup(APythonVersion, ACancelation); - try - InstallAndLoadPlugins(TPyPluginEvent.BeforeSetup, ACancelation); - - Prepare(ACancelation); - - LDistribution := FDistributions.LocateEnvironment( - IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion)); - - if not Assigned(LDistribution) then - Exit(false); - - Result := LDistribution.Setup(ACancelation); - - if not Result then - Exit; - - InstallAndLoadPlugins(TPyPluginEvent.AfterSetup, ACancelation); - except - on E: EAbort do - raise - else - DoInternalError(); - end; - - DoAfterSetup(APythonVersion, LDistribution, ACancelation); - - Result := true; -end; - -function TPyCustomEnvironment.InternalActivate(const APythonVersion: string; - const ACancelation: ICancelation): boolean; -var - LDistribution: TPyDistribution; -begin - Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); - - DoBeforeActivate(APythonVersion, ACancelation); - - try - InstallAndLoadPlugins(TPyPluginEvent.BeforeActivate, ACancelation); - except - on E: EAbort do - raise - else - DoInternalError(); - end; - - //An engine is required - if not Assigned(FPythonEngine) then - Exit(false); - - LDistribution := FDistributions.LocateEnvironment( - IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion)); - - if not Assigned(LDistribution) then - Exit(false); - - if not LDistribution.IsAvailable() then - Exit(false); - - try - FPythonEngine.UseLastKnownVersion := false; - FPythonEngine.PythonHome := TPyEnvironmentPath.ResolvePath(LDistribution.Home); - FPythonEngine.ProgramName := TPyEnvironmentPath.ResolvePath(LDistribution.Executable); - FPythonEngine.DllPath := TPath.GetDirectoryName(TPyEnvironmentPath.ResolvePath(LDistribution.SharedLibrary)); - FPythonEngine.DllName := TPath.GetFileName(LDistribution.SharedLibrary); - TThread.Synchronize(nil, procedure() begin - FPythonEngine.LoadDll(); - end); - - Result := FPythonEngine.IsHandleValid(); - - if not Result then - Exit; - except - on E: EAbort do - raise - else - DoInternalError(); - end; - - // Set the current path to the one we have rw permission - {$IFDEF ANDROID} - SetCurrentDir(FPythonEngine.PythonHome); - {$ENDIF ANDROID} - - DoAfterActivate(APythonVersion, LDistribution, Result, ACancelation); - - try - InstallAndLoadPlugins(TPyPluginEvent.AfterActivate, ACancelation); - except - on E: EAbort do - raise - else - DoInternalError(); - end; - - DoInternalReady(); -end; - -procedure TPyCustomEnvironment.InternalDeactivate( - const ACancelation: ICancelation); -begin - Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); - - DoBeforeDeactivate(ACancelation); - - //We need a working engine - if not Assigned(FPythonEngine) then - Exit(); - - try - InstallAndLoadPlugins(TPyPluginEvent.BeforeDeactivate, ACancelation); - - FPythonEngine.UnloadDll(); - FPythonEngine.PythonHome := String.Empty; - FPythonEngine.ProgramName := String.Empty; - FPythonEngine.DllPath := String.Empty; - FPythonEngine.DllName := String.Empty; - - InstallAndLoadPlugins(TPyPluginEvent.AfterDeactivate, ACancelation); - except - on E: EAbort do - raise - else - DoInternalError(); - end; - - DoAfterDeactivate(ACancelation); -end; - -procedure TPyCustomEnvironment.Prepare(const ACancelation: ICancelation); -begin - if PythonVersion.Trim().IsEmpty() and (Distributions.Count > 0) then - PythonVersion := TPyDistribution(Distributions.Items[0]).PythonVersion; -end; - -procedure TPyCustomEnvironment.AddPlugin(const APlugin: IPyEnvironmentPlugin); -begin - FPlugins.Add(APlugin); -end; - -procedure TPyCustomEnvironment.RemovePlugin(const APlugin: IPyEnvironmentPlugin); -begin - FPlugins.Remove(APlugin); -end; - -procedure TPyCustomEnvironment.InstallAndLoadPlugins(const AEvent: TPyPluginEvent; - const ACancelation: ICancelation); -var - LPlugin: IPyEnvironmentPlugin; -begin - for LPlugin in FPlugins do begin - if (AEvent in LPlugin.Info.InstallsWhen) and not LPlugin.IsInstalled() then begin - DoPluginInstall(LPlugin as TObject, LPlugin.Info); - LPlugin.InstallPlugin(ACancelation); - end; - - ACancelation.CheckCancelled(); - - if (AEvent in LPlugin.Info.LoadsWhen) and LPlugin.IsInstalled() then begin - DoPluginLoad(LPlugin as TObject, LPlugin.Info); - LPlugin.LoadPlugin(ACancelation); - end; - end; -end; - -procedure TPyCustomEnvironment.UninstallAndUnloadPlugins(const AEvent: TPyPluginEvent; - const ACancelation: ICancelation); -var - LPlugin: IPyEnvironmentPlugin; -begin - for LPlugin in FPlugins do begin - if (AEvent in LPlugin.Info.UnloadsWhen) and LPlugin.IsInstalled() then begin - DoPluginUnload(LPlugin as TObject, LPlugin.Info); - LPlugin.UnloadPlugin(ACancelation); - end; - - if not (AEvent in LPlugin.Info.UninstallsWhen) then - Continue; - - if not LPlugin.IsInstalled() then - Continue; - - DoPluginUninstall(LPlugin as TObject, LPlugin.Info); - LPlugin.UninstallPlugin(ACancelation); - end; -end; - -{ TPyEnvironment } - -procedure TPyEnvironment.DefineProperties(Filer: TFiler); -begin - inherited; -end; - -function TPyEnvironment.GetChildOwner: TComponent; -begin - Result := Self; -end; - -procedure TPyEnvironment.GetChildren(Proc: TGetChildProc; Root: TComponent); -//var -// I: Integer; -begin - inherited GetChildren(Proc, Root); -// for I := 0 to ComponentCount - 1 do -// Proc(Components[I]); -end; - -{ TPyCustomEnvironment.TEnvironmentTaskAsyncResult } - -constructor TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Create( - const AContext: TObject; const AAsyncTask: TProc; - const AAsyncCallback: TAsyncCallback); -begin - inherited Create(AContext); - FAsyncTask := AAsyncTask; - FAsyncCallback := AAsyncCallback; - FCancelation := TCancelation.Create(); -end; - -procedure TPyCustomEnvironment.TEnvironmentTaskAsyncResult.AsyncDispatch; -begin - FAsyncTask(FCancelation); -end; - -procedure TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Complete; -begin - inherited; - if Assigned(FAsyncCallback) then - FAsyncCallback(Self as IAsyncResult); -end; - -function TPyCustomEnvironment.TEnvironmentTaskAsyncResult.DoCancel: Boolean; -begin - FCancelation.Cancel(); - - Result := true; -end; - -procedure TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Schedule; -begin - TTask.Run(DoAsyncDispatch); -end; - -{ TPyCustomEnvironment.TEnvironmentTaskAsyncResult } - -constructor TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Create( - const AContext: TObject; const AAsyncTask: TFunc; - const AAsyncFuncCallback: TAsyncFuncCallback); -begin - inherited Create(AContext, - procedure(ACancelation: ICancelation) - begin - FRetVal := AAsyncTask(ACancelation); - end, - procedure(const AAsyncResult: IAsyncResult) - begin - if Assigned(AAsyncFuncCallback) then - AAsyncFuncCallback(AAsyncResult, FRetVal); - end); -end; - -function TPyCustomEnvironment.TEnvironmentTaskAsyncResult.GetRetVal: TResult; -begin - WaitForCompletion; - Result := FRetVal; -end; - -{ TPyCustomEnvironment.TAsyncActivate } - -procedure TPyCustomEnvironment.TAsyncActivate.AsyncDispatch; -begin - if (FSetupResult as TAsyncSetup).GetRetVal() then - if not FSetupResult.IsCancelled then - inherited - else - Cancel(); -end; - -constructor TPyCustomEnvironment.TAsyncActivate.Create(const AContext: TObject; - const ASetupResult: IAsyncResult; const AAsyncTask: TFunc; - const AAsyncFuncCallback: TAsyncFuncCallback); -begin - inherited Create(AContext, AAsyncTask, AAsyncFuncCallback); - FSetupResult := ASetupResult; -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyEnvironment' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: PyEnvironment layer *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyEnvironment; + +interface + +uses + System.Classes, + System.Rtti, + System.SysUtils, + System.Threading, + System.Generics.Collections, + System.Types, + System.SysConst, + PythonEngine, + PyTools.Cancelation, + PyEnvironment.Distribution; + +type + {$SCOPEDENUMS ON} + TPyPluginEvent = ( + BeforeSetup, AfterSetup, + BeforeActivate, AfterActivate, + BeforeDeactivate, AfterDeactivate); + {$SCOPEDENUMS OFF} + TPyPluginEvents = set of TPyPluginEvent; + + TPyPluginInfo = record + public + Name: string; + Description: string; + + InstallsWhen: TPyPluginEvents; + UninstallsWhen: TPyPluginEvents; + LoadsWhen: TPyPluginEvents; + UnloadsWhen: TPyPluginEvents; + end; + + TPyEnvironmentBeforeSetup = procedure(Sender: TObject; const APythonVersion: string) of object; + TPyEnvironmentAfterSetup = procedure(Sender: TObject; const APythonVersion: string) of object; + TPyEnvironmentBeforeActivate = procedure(Sender: TObject; const APythonVersion: string) of object; + TPyEnvironmentAfterActivate = procedure(Sender: TObject; const APythonVersion: string; const AActivated: boolean) of object; + TPyEnvironmentBeforeDeactivate = procedure(Sender: TObject; const APythonVersion: string) of object; + TPyEnvironmentAfterDeactivate = procedure(Sender: TObject; const APythonVersion: string) of object; + TPyEnvironmentReady = procedure(Sender: TObject; const APythonVersion: string) of object; + TPyEnvironmentError = procedure(Sender: TObject; const AException: Exception) of object; + //Plugins + TPyEnvironmentPluginInstall = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; + TPyEnvironmentPluginUninstall = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; + TPyEnvironmentPluginLoad = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; + TPyEnvironmentPluginUnload = procedure(const APlugin: TObject; const AInfo: TPyPluginInfo) of object; + + IPyEnvironmentPlugin = interface + ['{11906029-81A6-4F94-ACDA-2DBB346C6E3A}'] + function GetInfo(): TPyPluginInfo; + + procedure InstallPlugin(const ACancelation: ICancelation); + procedure UninstallPlugin(const ACancelation: ICancelation); + + procedure LoadPlugin(const ACancelation: ICancelation); + procedure UnloadPlugin(const ACancelation: ICancelation); + + function IsInstalled(): boolean; + + property Info: TPyPluginInfo read GetInfo; + end; + + TPyCustomEnvironment = class(TComponent) + protected type + TEnvironmentTaskAsyncResult = class(TBaseAsyncResult) + private + FAsyncTask: TProc; + FAsyncCallback: TAsyncCallback; + FCancelation: ICancelation; + protected + procedure Complete; override; + procedure Schedule; override; + procedure AsyncDispatch; override; + function DoCancel: Boolean; override; + public + constructor Create(const AContext: TObject; + const AAsyncTask: TProc; + const AAsyncCallback: TAsyncCallback = nil); + end; + + TAsyncFuncCallback = reference to procedure ( + const ASyncResult: IAsyncResult; const AResult: TResult); + + TEnvironmentTaskAsyncResult = class(TEnvironmentTaskAsyncResult) + private + FRetVal: TResult; + public + constructor Create(const AContext: TObject; const AAsyncTask: TFunc; + const AAsyncFuncCallback: TAsyncFuncCallback = nil); + function GetRetVal: TResult; + end; + + TAsyncSetup = class sealed(TEnvironmentTaskAsyncResult); + + TAsyncActivate = class sealed(TEnvironmentTaskAsyncResult) + private + FSetupResult: IAsyncResult; + protected + procedure AsyncDispatch; override; + public + constructor Create(const AContext: TObject; + const ASetupResult: IAsyncResult; + const AAsyncTask: TFunc; + const AAsyncFuncCallback: TAsyncFuncCallback = nil); + end; + private + FDistributions: TPyDistributionCollection; + FPlugins: TList; + FAutoLoad: boolean; + FPythonEngine: TPythonEngine; + FPythonVersion: string; + //Async options + FSynchronizeEvents: boolean; + //Events + FBeforeSetup: TPyEnvironmentBeforeSetup; + FAfterSetup: TPyEnvironmentAfterSetup; + FBeforeActivate: TPyEnvironmentBeforeActivate; + FAfterActivate: TPyEnvironmentAfterActivate; + FBeforeDeactivate: TPyEnvironmentBeforeDeactivate; + FAfterDeactivate: TPyEnvironmentAfterDeactivate; + FOnReady: TPyEnvironmentReady; + FOnError: TPyEnvironmentError; + //Plugin events + FOnPluginInstall: TPyEnvironmentPluginInstall; + FOnPluginUninstall: TPyEnvironmentPluginUninstall; + FOnPluginLoad: TPyEnvironmentPluginLoad; + FOnPluginUnload: TPyEnvironmentPluginUnload; + procedure SetDistributions(const ADistributions: TPyDistributionCollection); + procedure SetPythonEngine(const APythonEngine: TPythonEngine); + procedure DoAutoLoad; + //Hooked routines + function InternalSetup(const APythonVersion: string; + const ACancelation: ICancelation): boolean; + function InternalActivate(const APythonVersion: string; + const ACancelation: ICancelation): boolean; + procedure InternalDeactivate(const ACancelation: ICancelation); + //Event handlers + procedure DoBeforeSetup(APythonVersion: string; const ACancelation: ICancelation); + procedure DoAfterSetup(APythonVersion: string; + const ADistribution: TPyDistribution; const ACancelation: ICancelation); + procedure DoBeforeActivate(APythonVersion: string; const ACancelation: ICancelation); + procedure DoAfterActivate( + APythonVersion: string; const ADistribution: TPyDistribution; + var Result: Boolean; const ACancelation: ICancelation); + procedure DoBeforeDeactivate(const ACancelation: ICancelation); + procedure DoAfterDeactivate(const ACancelation: ICancelation); + procedure DoPluginInstall(const APlugin: TObject; const AInfo: TPyPluginInfo); + procedure DoPluginUninstall(const APlugin: TObject; const AInfo: TPyPluginInfo); + procedure DoPluginLoad(const APlugin: TObject; const AInfo: TPyPluginInfo); + procedure DoPluginUnload(const APlugin: TObject; const AInfo: TPyPluginInfo); + //It won't be ready until setup, activate, install and load plugins + procedure DoInternalReady; + procedure DoInternalError; + protected + procedure Loaded(); override; + procedure Notification(AComponent: TComponent; AOperation: TOperation); override; + procedure HandleEvent(const ASynchronizeEvents: boolean; + const AThreadProc: TThreadProcedure); + //Plugins install/uninstall/load/unload + procedure InstallAndLoadPlugins(const AEvent: TPyPluginEvent; + const ACancelation: ICancelation); virtual; + procedure UninstallAndUnloadPlugins(const AEvent: TPyPluginEvent; + const ACancelation: ICancelation); virtual; + protected + procedure SetPythonVersion(const Value: string); virtual; + function CreateCollection(): TPyDistributionCollection; virtual; abstract; + procedure Prepare(const ACancelation: ICancelation); virtual; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy(); override; + /// + /// Setup an environment. + /// + function Setup(APythonVersion: string = ''): boolean; + /// + /// Activate an environment. + /// + function Activate(APythonVersion: string = ''): boolean; + /// + /// Deactivate an environment. + /// + procedure Deactivate(); + + function SetupAsync(const APythonVersion: string; + const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; + function SetupAsync( + const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; + function ActivateAsync(const APythonVersion: string; + const ASetupAsync: IAsyncResult; + const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; + function ActivateAsync(const ASetupAsync: IAsyncResult; + const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; + function ActivateAsync( + const ACallback: TAsyncFuncCallback = nil): IAsyncResult; overload; + /// + /// Async begin setup an environment. + /// + function BeginSetup(const APythonVersion: string = ''): IAsyncResult; + /// + /// Wait for the async setup action to complete. + /// + function EndSetup(const ASyncResult: IAsyncResult): boolean; + /// + /// Queue the activate action to the main thread. + /// + function BeginActivate(const APythonVersion: string = ''): IAsyncResult; + /// + /// Wait for the activate action to complete. + /// Never call this routine directly from main thread. + /// + function EndActivate(const ASyncResult: IAsyncResult): boolean; + //Plugins + procedure AddPlugin(const APlugin: IPyEnvironmentPlugin); + procedure RemovePlugin(const APlugin: IPyEnvironmentPlugin); + public + property Distributions: TPyDistributionCollection read FDistributions write SetDistributions; + property AutoLoad: boolean read FAutoLoad write FAutoLoad; + property PythonVersion: string read FPythonVersion write SetPythonVersion; + property PythonEngine: TPythonEngine read FPythonEngine write SetPythonEngine; + /// + /// Automatically sync events when running async. + /// + property SynchronizeEvents: boolean read FSynchronizeEvents write FSynchronizeEvents default true; + published + property BeforeSetup: TPyEnvironmentBeforeSetup read FBeforeSetup write FBeforeSetup; + property AfterSetup: TPyEnvironmentAfterSetup read FAfterSetup write FAfterSetup; + property BeforeActivate: TPyEnvironmentBeforeActivate read FBeforeActivate write FBeforeActivate; + property AfterActivate: TPyEnvironmentAfterActivate read FAfterActivate write FAfterActivate; + property BeforeDeactivate: TPyEnvironmentBeforeDeactivate read FBeforeDeactivate write FBeforeDeactivate; + property AfterDeactivate: TPyEnvironmentAfterDeactivate read FAfterDeactivate write FAfterDeactivate; + property OnError: TPyEnvironmentError read FOnError write FOnError; + property OnReady: TPyEnvironmentReady read FOnReady write FOnReady; + //Plugin events + property OnPluginInstall: TPyEnvironmentPluginInstall read FOnPluginInstall write FOnPluginInstall; + property OnPluginUninstall: TPyEnvironmentPluginUninstall read FOnPluginUninstall write FOnPluginUninstall; + property OnPluginLoad: TPyEnvironmentPluginLoad read FOnPluginLoad write FOnPluginLoad; + property OnPluginUnload: TPyEnvironmentPluginUnload read FOnPluginUnload write FOnPluginUnload; + end; + + TPyEnvironment = class(TPyCustomEnvironment) + private + protected + procedure DefineProperties(Filer: TFiler); override; + procedure GetChildren(Proc: TGetChildProc; Root: TComponent); override; + function GetChildOwner: TComponent; override; + published + property AutoLoad; + property SynchronizeEvents; + property PythonVersion; + property PythonEngine; + end; + +implementation + +uses + System.IOUtils, + System.StrUtils, + TypInfo, + PyEnvironment.Path; + +{ TPyCustomEnvironment } + +constructor TPyCustomEnvironment.Create(AOwner: TComponent); +begin + inherited; + FDistributions := CreateCollection(); + FPlugins := TList.Create(); + FSynchronizeEvents := true; +end; + +destructor TPyCustomEnvironment.Destroy; +begin + FPlugins.Free(); + FDistributions.Free(); + SetPythonEngine(nil); + inherited; +end; + +procedure TPyCustomEnvironment.Loaded; +begin + inherited; + DoAutoLoad(); +end; + +procedure TPyCustomEnvironment.Notification(AComponent: TComponent; + AOperation: TOperation); +begin + inherited; + if (AOperation = opRemove) then begin + if (AComponent = FPythonEngine) then + FPythonEngine := nil; + end; +end; + +procedure TPyCustomEnvironment.SetDistributions( + const ADistributions: TPyDistributionCollection); +begin + FDistributions.Assign(ADistributions); +end; + +procedure TPyCustomEnvironment.SetPythonEngine(const APythonEngine: TPythonEngine); +begin + if (APythonEngine <> FPythonEngine) then begin + if Assigned(FPythonEngine) then + FPythonEngine.RemoveFreeNotification(Self); + FPythonEngine := APythonEngine; + if Assigned(FPythonEngine) then begin + FPythonEngine.FreeNotification(Self); + if (csDesigning in ComponentState) then + FPythonEngine.AutoLoad := false; + end; + end; +end; + +procedure TPyCustomEnvironment.SetPythonVersion(const Value: string); +begin + FPythonVersion := Value.Trim(); +end; + +procedure TPyCustomEnvironment.DoAutoLoad; +begin + if not (csDesigning in ComponentState) and FAutoLoad + and not (PythonVersion.IsEmpty) then + if Setup(FPythonVersion) then + Activate(FPythonVersion); +end; + +function TPyCustomEnvironment.Setup(APythonVersion: string): boolean; +var + LCancelation: ICancelation; +begin + LCancelation := TCancelation.Create(); + Result := InternalSetup(APythonVersion, LCancelation); +end; + +function TPyCustomEnvironment.SetupAsync( + const ACallback: TAsyncFuncCallback): IAsyncResult; +begin + Result := SetupAsync(PythonVersion, ACallback); +end; + +function TPyCustomEnvironment.SetupAsync(const APythonVersion: string; + const ACallback: TAsyncFuncCallback): IAsyncResult; +begin + Result := TAsyncSetup.Create(Self, + function(ACancelation: ICancelation): boolean + begin + Result := InternalSetup( + IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion), + ACancelation); + end).Invoke(); +end; + +function TPyCustomEnvironment.BeginSetup(const APythonVersion: string): IAsyncResult; +begin + Result := SetupAsync(APythonVersion); +end; + +function TPyCustomEnvironment.EndSetup(const ASyncResult: IAsyncResult): boolean; +begin + Result := (ASyncResult as TAsyncSetup).GetRetVal(); +end; + +function TPyCustomEnvironment.BeginActivate( + const APythonVersion: string): IAsyncResult; +begin + Result := ActivateAsync(APythonVersion, nil, nil); +end; + +function TPyCustomEnvironment.EndActivate( + const ASyncResult: IAsyncResult): boolean; +begin + Result := TAsyncActivate(ASyncResult).GetRetVal(); +end; + +function TPyCustomEnvironment.Activate(APythonVersion: string): boolean; +var + LCancelation: ICancelation; +begin + LCancelation := TCancelation.Create(); + + Result := InternalActivate( + IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion), + LCancelation); +end; + +function TPyCustomEnvironment.ActivateAsync(const APythonVersion: string; + const ASetupAsync: IAsyncResult; const ACallback: TAsyncFuncCallback): IAsyncResult; +begin + Result := TAsyncActivate.Create(Self, ASetupAsync, + function(ACancelation: ICancelation): boolean + begin + Result := InternalActivate( + IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion), + ACancelation); + end, ACallback).Invoke(); +end; + +function TPyCustomEnvironment.ActivateAsync(const ASetupAsync: IAsyncResult; + const ACallback: TAsyncFuncCallback): IAsyncResult; +begin + Result := ActivateAsync(PythonVersion, ASetupAsync, ACallback); +end; + +function TPyCustomEnvironment.ActivateAsync( + const ACallback: TAsyncFuncCallback): IAsyncResult; +begin + Result := ActivateAsync(nil, ACallback); +end; + +procedure TPyCustomEnvironment.Deactivate; +var + LCancelation: ICancelation; +begin + LCancelation := TCancelation.Create(); + + InternalDeactivate(LCancelation); +end; + +procedure TPyCustomEnvironment.DoBeforeSetup(APythonVersion: string; + const ACancelation: ICancelation); +begin + if Assigned(FBeforeSetup) then + HandleEvent(FSynchronizeEvents, procedure() begin + FBeforeSetup(Self, APythonVersion); + end); +end; + +procedure TPyCustomEnvironment.DoAfterSetup(APythonVersion: string; + const ADistribution: TPyDistribution; const ACancelation: ICancelation); +begin + if Assigned(FAfterSetup) then + HandleEvent(FSynchronizeEvents, procedure() begin + FAfterSetup(Self, APythonVersion); + end); +end; + +procedure TPyCustomEnvironment.DoBeforeActivate(APythonVersion: string; + const ACancelation: ICancelation); +begin + if Assigned(FBeforeActivate) then + HandleEvent(FSynchronizeEvents, procedure() begin + FBeforeActivate(Self, APythonVersion); + end); +end; + +procedure TPyCustomEnvironment.DoAfterActivate(APythonVersion: string; + const ADistribution: TPyDistribution; var Result: Boolean; + const ACancelation: ICancelation); +var + LResult: Boolean; +begin + LResult := Result; + if Assigned(FAfterActivate) then + HandleEvent(FSynchronizeEvents, procedure() begin + FAfterActivate(Self, APythonVersion, LResult); + end); + Result := LResult; +end; + +procedure TPyCustomEnvironment.DoBeforeDeactivate( + const ACancelation: ICancelation); +begin + if Assigned(FBeforeDeactivate) then + HandleEvent(FSynchronizeEvents, procedure() begin + FBeforeDeactivate(Self, FPythonEngine.RegVersion); + end); +end; + +procedure TPyCustomEnvironment.DoAfterDeactivate( + const ACancelation: ICancelation); +begin + if Assigned(FAfterDeactivate) then + HandleEvent(FSynchronizeEvents, procedure() begin + FAfterDeactivate(Self, FPythonEngine.RegVersion); + end); +end; + +procedure TPyCustomEnvironment.DoPluginInstall(const APlugin: TObject; + const AInfo: TPyPluginInfo); +begin + if Assigned(FOnPluginInstall) then + HandleEvent(FSynchronizeEvents, procedure() begin + FOnPluginInstall(APlugin, AInfo); + end); +end; + +procedure TPyCustomEnvironment.DoPluginUninstall(const APlugin: TObject; + const AInfo: TPyPluginInfo); +begin + if Assigned(FOnPluginUninstall) then + HandleEvent(FSynchronizeEvents, procedure() begin + FOnPluginUninstall(APlugin, AInfo); + end); +end; + +procedure TPyCustomEnvironment.DoPluginLoad(const APlugin: TObject; + const AInfo: TPyPluginInfo); +begin + if Assigned(FOnPluginLoad) then + HandleEvent(FSynchronizeEvents, procedure() begin + FOnPluginLoad(APlugin, AInfo); + end); +end; + +procedure TPyCustomEnvironment.DoPluginUnload(const APlugin: TObject; + const AInfo: TPyPluginInfo); +begin + if Assigned(FOnPluginUnload) then + HandleEvent(FSynchronizeEvents, procedure() begin + FOnPluginUnload(APlugin, AInfo); + end); +end; + +procedure TPyCustomEnvironment.DoInternalReady; +begin + if Assigned(FOnReady) then + TThread.Synchronize(nil, procedure() begin + FOnReady(Self, FPythonEngine.RegVersion); + end); +end; + +procedure TPyCustomEnvironment.DoInternalError; +begin + if Assigned(FOnError) then begin + HandleEvent(FSynchronizeEvents, + procedure() + var + LException: Exception; + begin + LException := Exception(ExceptObject()); + FOnError(Self, LException); + end); + + Abort(); + end else begin + try + raise Exception(AcquireExceptionObject()) at ExceptAddr(); + finally + ReleaseExceptionObject(); + end; + end; +end; + +procedure TPyCustomEnvironment.HandleEvent(const ASynchronizeEvents: boolean; + const AThreadProc: TThreadProcedure); +begin + if ASynchronizeEvents and Assigned(WakeMainThread) and + (MainThreadID <> TThread.Current.ThreadID) then + TThread.Synchronize(TThread.Current, AThreadProc) + else + AThreadProc(); +end; + +function TPyCustomEnvironment.InternalSetup(const APythonVersion: string; + const ACancelation: ICancelation): boolean; +var + LDistribution: TPyDistribution; +begin + Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); + + LDistribution := nil; + + DoBeforeSetup(APythonVersion, ACancelation); + try + InstallAndLoadPlugins(TPyPluginEvent.BeforeSetup, ACancelation); + + Prepare(ACancelation); + + LDistribution := FDistributions.LocateEnvironment( + IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion)); + + if not Assigned(LDistribution) then + Exit(false); + + Result := LDistribution.Setup(ACancelation); + + if not Result then + Exit; + + InstallAndLoadPlugins(TPyPluginEvent.AfterSetup, ACancelation); + except + on E: EAbort do + raise + else + DoInternalError(); + end; + + DoAfterSetup(APythonVersion, LDistribution, ACancelation); + + Result := true; +end; + +function TPyCustomEnvironment.InternalActivate(const APythonVersion: string; + const ACancelation: ICancelation): boolean; +var + LDistribution: TPyDistribution; +begin + Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); + + DoBeforeActivate(APythonVersion, ACancelation); + + try + InstallAndLoadPlugins(TPyPluginEvent.BeforeActivate, ACancelation); + except + on E: EAbort do + raise + else + DoInternalError(); + end; + + //An engine is required + if not Assigned(FPythonEngine) then + Exit(false); + + LDistribution := FDistributions.LocateEnvironment( + IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion)); + + if not Assigned(LDistribution) then + Exit(false); + + if not LDistribution.IsAvailable() then + Exit(false); + + try + FPythonEngine.UseLastKnownVersion := false; + FPythonEngine.PythonHome := TPyEnvironmentPath.ResolvePath(LDistribution.Home); + FPythonEngine.DllPath := TPath.GetDirectoryName(TPyEnvironmentPath.ResolvePath(LDistribution.SharedLibrary)); + FPythonEngine.DllName := TPath.GetFileName(LDistribution.SharedLibrary); + {$IFNDEF IOS64} + FPythonEngine.ProgramName := TPyEnvironmentPath.ResolvePath(LDistribution.Executable); + {$ELSE} + FPythonEngine.PythonPath := TPyEnvironmentPath.ResolvePath(LDistribution.Path); + {$ENDIF} + TThread.Synchronize(nil, procedure() begin + FPythonEngine.LoadDll(); + end); + + Result := FPythonEngine.IsHandleValid(); + + if not Result then + Exit; + except + on E: EAbort do + raise + else + DoInternalError(); + end; + + // Set the current path to the one we have rw permission + {$IFDEF ANDROID} + SetCurrentDir(FPythonEngine.PythonHome); + {$ENDIF ANDROID} + + DoAfterActivate(APythonVersion, LDistribution, Result, ACancelation); + + try + InstallAndLoadPlugins(TPyPluginEvent.AfterActivate, ACancelation); + except + on E: EAbort do + raise + else + DoInternalError(); + end; + + DoInternalReady(); +end; + +procedure TPyCustomEnvironment.InternalDeactivate( + const ACancelation: ICancelation); +begin + Assert(Assigned(ACancelation), 'Invalid argument "ACancelation".'); + + DoBeforeDeactivate(ACancelation); + + //We need a working engine + if not Assigned(FPythonEngine) then + Exit(); + + try + InstallAndLoadPlugins(TPyPluginEvent.BeforeDeactivate, ACancelation); + + FPythonEngine.UnloadDll(); + FPythonEngine.PythonHome := String.Empty; + FPythonEngine.ProgramName := String.Empty; + FPythonEngine.DllPath := String.Empty; + FPythonEngine.DllName := String.Empty; + + InstallAndLoadPlugins(TPyPluginEvent.AfterDeactivate, ACancelation); + except + on E: EAbort do + raise + else + DoInternalError(); + end; + + DoAfterDeactivate(ACancelation); +end; + +procedure TPyCustomEnvironment.Prepare(const ACancelation: ICancelation); +begin + if PythonVersion.Trim().IsEmpty() and (Distributions.Count > 0) then + PythonVersion := TPyDistribution(Distributions.Items[0]).PythonVersion; +end; + +procedure TPyCustomEnvironment.AddPlugin(const APlugin: IPyEnvironmentPlugin); +begin + FPlugins.Add(APlugin); +end; + +procedure TPyCustomEnvironment.RemovePlugin(const APlugin: IPyEnvironmentPlugin); +begin + FPlugins.Remove(APlugin); +end; + +procedure TPyCustomEnvironment.InstallAndLoadPlugins(const AEvent: TPyPluginEvent; + const ACancelation: ICancelation); +var + LPlugin: IPyEnvironmentPlugin; +begin + for LPlugin in FPlugins do begin + if (AEvent in LPlugin.Info.InstallsWhen) and not LPlugin.IsInstalled() then begin + DoPluginInstall(LPlugin as TObject, LPlugin.Info); + LPlugin.InstallPlugin(ACancelation); + end; + + ACancelation.CheckCancelled(); + + if (AEvent in LPlugin.Info.LoadsWhen) and LPlugin.IsInstalled() then begin + DoPluginLoad(LPlugin as TObject, LPlugin.Info); + LPlugin.LoadPlugin(ACancelation); + end; + end; +end; + +procedure TPyCustomEnvironment.UninstallAndUnloadPlugins(const AEvent: TPyPluginEvent; + const ACancelation: ICancelation); +var + LPlugin: IPyEnvironmentPlugin; +begin + for LPlugin in FPlugins do begin + if (AEvent in LPlugin.Info.UnloadsWhen) and LPlugin.IsInstalled() then begin + DoPluginUnload(LPlugin as TObject, LPlugin.Info); + LPlugin.UnloadPlugin(ACancelation); + end; + + if not (AEvent in LPlugin.Info.UninstallsWhen) then + Continue; + + if not LPlugin.IsInstalled() then + Continue; + + DoPluginUninstall(LPlugin as TObject, LPlugin.Info); + LPlugin.UninstallPlugin(ACancelation); + end; +end; + +{ TPyEnvironment } + +procedure TPyEnvironment.DefineProperties(Filer: TFiler); +begin + inherited; +end; + +function TPyEnvironment.GetChildOwner: TComponent; +begin + Result := Self; +end; + +procedure TPyEnvironment.GetChildren(Proc: TGetChildProc; Root: TComponent); +//var +// I: Integer; +begin + inherited GetChildren(Proc, Root); +// for I := 0 to ComponentCount - 1 do +// Proc(Components[I]); +end; + +{ TPyCustomEnvironment.TEnvironmentTaskAsyncResult } + +constructor TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Create( + const AContext: TObject; const AAsyncTask: TProc; + const AAsyncCallback: TAsyncCallback); +begin + inherited Create(AContext); + FAsyncTask := AAsyncTask; + FAsyncCallback := AAsyncCallback; + FCancelation := TCancelation.Create(); +end; + +procedure TPyCustomEnvironment.TEnvironmentTaskAsyncResult.AsyncDispatch; +begin + FAsyncTask(FCancelation); +end; + +procedure TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Complete; +begin + inherited; + if Assigned(FAsyncCallback) then + FAsyncCallback(Self as IAsyncResult); +end; + +function TPyCustomEnvironment.TEnvironmentTaskAsyncResult.DoCancel: Boolean; +begin + FCancelation.Cancel(); + + Result := true; +end; + +procedure TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Schedule; +begin + TTask.Run(DoAsyncDispatch); +end; + +{ TPyCustomEnvironment.TEnvironmentTaskAsyncResult } + +constructor TPyCustomEnvironment.TEnvironmentTaskAsyncResult.Create( + const AContext: TObject; const AAsyncTask: TFunc; + const AAsyncFuncCallback: TAsyncFuncCallback); +begin + inherited Create(AContext, + procedure(ACancelation: ICancelation) + begin + FRetVal := AAsyncTask(ACancelation); + end, + procedure(const AAsyncResult: IAsyncResult) + begin + if Assigned(AAsyncFuncCallback) then + AAsyncFuncCallback(AAsyncResult, FRetVal); + end); +end; + +function TPyCustomEnvironment.TEnvironmentTaskAsyncResult.GetRetVal: TResult; +begin + WaitForCompletion; + Result := FRetVal; +end; + +{ TPyCustomEnvironment.TAsyncActivate } + +procedure TPyCustomEnvironment.TAsyncActivate.AsyncDispatch; +begin + if (FSetupResult as TAsyncSetup).GetRetVal() then + if not FSetupResult.IsCancelled then + inherited + else + Cancel(); +end; + +constructor TPyCustomEnvironment.TAsyncActivate.Create(const AContext: TObject; + const ASetupResult: IAsyncResult; const AAsyncTask: TFunc; + const AAsyncFuncCallback: TAsyncFuncCallback); +begin + inherited Create(AContext, AAsyncTask, AAsyncFuncCallback); + FSetupResult := ASetupResult; +end; + +end. diff --git a/src/Tools/ExecCmd/PyTools.ExecCmd.Args.pas b/src/Tools/ExecCmd/PyTools.ExecCmd.Args.pas index 902b8d3..17a9f46 100644 --- a/src/Tools/ExecCmd/PyTools.ExecCmd.Args.pas +++ b/src/Tools/ExecCmd/PyTools.ExecCmd.Args.pas @@ -1,83 +1,83 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyTools.ExecCmd.Args' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Supplier for Execute Commands *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyTools.ExecCmd.Args; - -interface - -type - TExecCmdArgs = class - public - class function BuildArgv(const AExecutable: string; - const AInput: TArray): TArray; - class function BuildEnvp(const AHome, AExecutable, - ASharedLibrary: string): TArray; - end; - -implementation - -uses - System.IOUtils, System.SysUtils; - -{ TExecCmdArgs } - -class function TExecCmdArgs.BuildArgv(const AExecutable: string; - const AInput: TArray): TArray; -begin - {$IFDEF MSWINDOWS} - Result := AInput; - {$ELSE} - Result := [AExecutable] + AInput; - {$ENDIF MSWINDOWS} -end; - -class function TExecCmdArgs.BuildEnvp(const AHome, AExecutable, - ASharedLibrary: string): TArray; -begin - {$IFDEF MSWINDOWS} - Result := []; - {$ELSEIF DEFINED(OSX)} - Result := ['DYLD_LIBRARY_PATH=' - + ExtractFileDir(ASharedLibrary) - + ':' - + ExtractFileDir(AExecutable), - 'LD_LIBRARY_PATH=' + ExtractFileDir(ASharedLibrary), - 'PATH=' + ExtractFileDir(AExecutable)]; - {$ELSEIF DEFINED(POSIX)} - Result := ['LD_LIBRARY_PATH=' + ExtractFileDir(ASharedLibrary), - 'PYTHONHOME=' + AHome, - 'PATH=' + ExtractFileDir(AExecutable)]; - {$IFDEF ANDROID} - Result := Result + ['TMPDIR=' + TPath.GetTempPath()]; - {$ENDIF ANDROID} - {$ENDIF MSWINDOWS} -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyTools.ExecCmd.Args' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Supplier for Execute Commands *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyTools.ExecCmd.Args; + +interface + +type + TExecCmdArgs = class + public + class function BuildArgv(const AExecutable: string; + const AInput: TArray): TArray; + class function BuildEnvp(const AHome, AExecutable, + ASharedLibrary: string): TArray; + end; + +implementation + +uses + System.IOUtils, System.SysUtils; + +{ TExecCmdArgs } + +class function TExecCmdArgs.BuildArgv(const AExecutable: string; + const AInput: TArray): TArray; +begin + {$IFDEF MSWINDOWS} + Result := AInput; + {$ELSE} + Result := [AExecutable] + AInput; + {$ENDIF MSWINDOWS} +end; + +class function TExecCmdArgs.BuildEnvp(const AHome, AExecutable, + ASharedLibrary: string): TArray; +begin + {$IFDEF MSWINDOWS} + Result := []; + {$ELSEIF DEFINED(OSX)} + Result := ['DYLD_LIBRARY_PATH=' + + ExtractFileDir(ASharedLibrary) + + ':' + + ExtractFileDir(AExecutable), + 'LD_LIBRARY_PATH=' + ExtractFileDir(ASharedLibrary), + 'PATH=' + ExtractFileDir(AExecutable)]; + {$ELSEIF DEFINED(POSIX)} + Result := ['LD_LIBRARY_PATH=' + ExtractFileDir(ASharedLibrary), + 'PYTHONHOME=' + AHome, + 'PATH=' + ExtractFileDir(AExecutable)]; + {$IFDEF ANDROID} + Result := Result + ['TMPDIR=' + TPath.GetTempPath()]; + {$ENDIF ANDROID} + {$ENDIF MSWINDOWS} +end; + +end. diff --git a/src/Tools/ExecCmd/PyTools.ExecCmd.Platform.pas b/src/Tools/ExecCmd/PyTools.ExecCmd.Platform.pas index a0452f9..bc12817 100644 --- a/src/Tools/ExecCmd/PyTools.ExecCmd.Platform.pas +++ b/src/Tools/ExecCmd/PyTools.ExecCmd.Platform.pas @@ -1,39 +1,39 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyTools.ExecCmd.Platform' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Execute Shell Commands and/or Subprocess *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyTools.ExecCmd.Platform; - -{$DEFINE PLATFORM_UNIT} - -{$IF DEFINED(MSWINDOWS)} - {$I PyTools.ExecCmd.Win.pas} -{$ELSEIF DEFINED(POSIX)} - {$I PyTools.ExecCmd.Posix.pas} -{$ENDIF} +(**************************************************************************) +(* *) +(* Module: Unit 'PyTools.ExecCmd.Platform' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Execute Shell Commands and/or Subprocess *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyTools.ExecCmd.Platform; + +{$DEFINE PLATFORM_UNIT} + +{$IF DEFINED(MSWINDOWS)} + {$I PyTools.ExecCmd.Win.pas} +{$ELSEIF DEFINED(POSIX)} + {$I PyTools.ExecCmd.Posix.pas} +{$ENDIF} diff --git a/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.Local.pas b/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.Local.pas index d6d18fc..617992c 100644 --- a/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.Local.pas +++ b/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.Local.pas @@ -1,423 +1,423 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyTools.Protocol.Local' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Notification protocol implementation for *) -(* local messages *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyTools.Notification.Protocol.Local; - -interface - -uses - System.Types, - System.Classes, - System.SysUtils, - System.Threading, - System.Generics.Collections, - PyTools.Notification.Channel, - PyTools.Notification.Protocol; - -type - TLocalNotificationProtocol = class(TInterfacedObject, IClientNotificationProtocol, IServerNotificationProtocol) - private type - TLocalQueue = class(TQueue); - - TLocalClient = class(TBaseClientNotificationProtocol) - private - [Unsafe] - FParent: TLocalNotificationProtocol; - FTask: TThread; - protected - function TransmitContent(const AContent: TNotificationChannel): IAsyncResult; override; - function ReceiveContent(): IAsyncResult; override; - public - constructor Create(const AParent: TLocalNotificationProtocol); reintroduce; - destructor Destroy(); override; - end; - - TLocalServer = class(TBaseServerNotificationProtocol) - private - [Unsafe] - FParent: TLocalNotificationProtocol; - FTask: TThread; - protected - function TransmitContent(const AContent: TNotificationChannel): IAsyncResult; override; - function ReceiveContent(): IAsyncResult; override; - public - constructor Create(const AParent: TLocalNotificationProtocol); reintroduce; - destructor Destroy(); override; - end; - - TNotificationAsyncResult = class(TBaseAsyncResult) - private - FAsyncTask: TProc; - protected - procedure Schedule; override; - procedure AsyncDispatch; override; - public - constructor Create(const AContext: TObject; const AAsyncTask: TProc); - end; - - TNotificationAsyncResult = class(TNotificationAsyncResult) - private - FRetVal: TResult; - public - function GetRetVal: TResult; - end; - private - // Protocols - FClient: IClientNotificationProtocol; - FServer: IServerNotificationProtocol; - // Shared queues - FClientQueue: TLocalQueue; - FServerQueue: TLocalQueue; - procedure ClearQueue(const AQueue: TLocalQueue); - private - property Client: IClientNotificationProtocol read FClient - implements IClientNotificationProtocol; - property Server: IServerNotificationProtocol read FServer - implements IServerNotificationProtocol; - public - constructor Create(); - destructor Destroy(); override; - - /// - /// Event subscription. - /// - function SubscribeToRequest(const ARequestChannelIdentifier: TRequestChannelIdentifier; - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; - /// - /// Generic event subscription. - /// - function SubscribeToRequest( - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; - /// - /// Event broadcaster. - /// - function BroadcastEvent(const AEvent: TEventChannel): IAwait; - - /// - /// Event subscription. - /// - function SubscribeToEvent(const AEventChannelIdentifier: TEventChannelIdentifier; - const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; - /// - /// Generic event subscription. - /// - function SubscribeToEvent( - const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; - /// - /// Request handler. - /// Use IAwait to guarantee message delivery. - /// - function SendRequest(const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; overload; - /// - /// Generic request handler. - /// Use IAwait to guarantee message delivery. - /// - function SendRequest(const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; overload; - end; - - TAppNotificationProtocol = class - private - class var FInstance: TLocalNotificationProtocol; - class function GetInstance: TLocalNotificationProtocol; static; - public - class destructor Destroy(); - - class property Instance: TLocalNotificationProtocol read GetInstance; - end; - -implementation - -{ TLocalNotificationProtocol.TLocalClient } - -constructor TLocalNotificationProtocol.TLocalClient.Create( - const AParent: TLocalNotificationProtocol); -begin - inherited Create(); - FParent := AParent; - FTask := TThread.CreateAnonymousThread( - procedure() - var - LAsync: IAsyncResult; - LNotification: TNotificationChannel; - begin - while not FTask.CheckTerminated() do begin - if not Enabled then begin - Sleep(100); - Continue; - end; - - try - LAsync := ReceiveContent(); - LNotification := TNotificationAsyncResult(LAsync).GetRetVal(); - if Assigned(LNotification) then begin - try - Process(LNotification); - finally - LNotification.Free(); - end; - end; - except - // - end; - Sleep(100); - end; - TThread.RemoveQueuedEvents(TThread.Current); - end); - FTask.FreeOnTerminate := false; - FTask.Start(); -end; - -destructor TLocalNotificationProtocol.TLocalClient.Destroy; -begin - FTask.Terminate(); - FTask.WaitFor(); - FTask.Free(); - inherited; -end; - -function TLocalNotificationProtocol.TLocalClient.ReceiveContent: IAsyncResult; -var - LAsyncResult: TNotificationAsyncResult; -begin - LAsyncResult := TNotificationAsyncResult.Create(Self, - procedure() - begin - if (FParent.FClientQueue.Count > 0) then - LAsyncResult.FRetVal := FParent.FClientQueue.Dequeue() - else - LAsyncResult.FRetVal := nil; - end); - - Result := LAsyncResult.Invoke(); -end; - -function TLocalNotificationProtocol.TLocalClient.TransmitContent( - const AContent: TNotificationChannel): IAsyncResult; -begin - Result := TNotificationAsyncResult.Create(Self, procedure() begin - FParent.FServerQueue.Enqueue(AContent); - end).Invoke(); -end; - -{ TLocalNotificationProtocol.TLocalServer } - -constructor TLocalNotificationProtocol.TLocalServer.Create( - const AParent: TLocalNotificationProtocol); -begin - inherited Create(); - FParent := AParent; - FTask := TThread.CreateAnonymousThread( - procedure() - var - LAsync: IAsyncResult; - LNotification: TNotificationChannel; - begin - while not FTask.CheckTerminated() do begin - if not Enabled then begin - Sleep(100); - Continue; - end; - - LAsync := ReceiveContent(); - LNotification := TNotificationAsyncResult(LAsync).GetRetVal(); - if Assigned(LNotification) then begin - try - Process(LNotification); - finally - LNotification.Free(); - end; - end; - Sleep(100); - end; - TThread.RemoveQueuedEvents(TThread.Current); - end); - FTask.FreeOnTerminate := false; - FTask.Start(); -end; - -destructor TLocalNotificationProtocol.TLocalServer.Destroy; -begin - FTask.Terminate(); - FTask.WaitFor(); - FTask.Free(); - inherited; -end; - -function TLocalNotificationProtocol.TLocalServer.ReceiveContent: IAsyncResult; -var - LAsyncResult: TNotificationAsyncResult; -begin - LAsyncResult := TNotificationAsyncResult.Create(Self, - procedure() - begin - if (FParent.FServerQueue.Count > 0) then - LAsyncResult.FRetVal := FParent.FServerQueue.Dequeue - else - LAsyncResult.FRetVal := nil; - end); - - Result := LAsyncResult.Invoke(); -end; - -function TLocalNotificationProtocol.TLocalServer.TransmitContent( - const AContent: TNotificationChannel): IAsyncResult; -begin - Result := TNotificationAsyncResult.Create(Self, procedure() begin - FParent.FClientQueue.Enqueue(AContent); - end).Invoke(); -end; - -{ TLocalNotificationProtocol.TNotificationAsyncResult } - -constructor TLocalNotificationProtocol.TNotificationAsyncResult.Create( - const AContext: TObject; const AAsyncTask: TProc); -begin - inherited Create(AContext); - FAsyncTask := AAsyncTask; -end; - -procedure TLocalNotificationProtocol.TNotificationAsyncResult.Schedule; -begin - TThread.ForceQueue(TThread.Current, DoAsyncDispatch); -end; - -procedure TLocalNotificationProtocol.TNotificationAsyncResult.AsyncDispatch; -begin - FAsyncTask(); -end; - -{ TLocalNotificationProtocol.TNotificationAsyncResult } - -function TLocalNotificationProtocol.TNotificationAsyncResult.GetRetVal: TResult; -begin - WaitForCompletion; - Result := FRetVal; -end; - -{ TLocalNotificationProtocol } - -procedure TLocalNotificationProtocol.ClearQueue(const AQueue: TLocalQueue); -begin - while (AQueue.Count > 0) do - AQueue.Dequeue().Free(); -end; - -constructor TLocalNotificationProtocol.Create; -begin - inherited; - FClientQueue := TLocalQueue.Create(); - FServerQueue := TLocalQueue.Create(); - FClient := TLocalClient.Create(Self); - FServer := TLocalServer.Create(Self); -end; - -destructor TLocalNotificationProtocol.Destroy; -begin - FServer := nil; - FClient := nil; - ClearQueue(FServerQueue); - ClearQueue(FClientQueue); - FServerQueue.Free(); - FClientQueue.Free(); - inherited; -end; - -function TLocalNotificationProtocol.BroadcastEvent( - const AEvent: TEventChannel): IAwait; -begin - Result := (FServer as TBaseServerNotificationProtocol).BroadcastEvent(AEvent); -end; - -function TLocalNotificationProtocol.SubscribeToEvent( - const AEventChannelIdentifier: TEventChannelIdentifier; - const AEventNotification: TEventNotificationCallback): IDisconnectable; -begin - Result := (FClient as TBaseClientNotificationProtocol).SubscribeToEvent( - AEventChannelIdentifier, AEventNotification); -end; - -function TLocalNotificationProtocol.SubscribeToEvent( - const AEventNotification: TEventNotificationCallback): IDisconnectable; -begin - Result := (FClient as TBaseClientNotificationProtocol).SubscribeToEvent( - AEventNotification); -end; - -function TLocalNotificationProtocol.SendRequest(const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; -begin - Result := (FClient as TBaseClientNotificationProtocol).SendRequest(ARequest, - AAccept, AReject); -end; - -function TLocalNotificationProtocol.SendRequest( - const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; -begin - Result := (FClient as TBaseClientNotificationProtocol).SendRequest( - ARequest, AAccept, AReject); -end; - -function TLocalNotificationProtocol.SubscribeToRequest( - const ARequestChannelIdentifier: TRequestChannelIdentifier; - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; -begin - Result := (FServer as TBaseServerNotificationProtocol).SubscribeToRequest( - ARequestChannelIdentifier, ARequestNotification); -end; - -function TLocalNotificationProtocol.SubscribeToRequest( - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; -begin - Result := (FServer as TBaseServerNotificationProtocol).SubscribeToRequest( - ARequestNotification); -end; - -{ TAppNotificationProtocol } - -class destructor TAppNotificationProtocol.Destroy; -begin - FInstance.Free(); -end; - -class function TAppNotificationProtocol.GetInstance: TLocalNotificationProtocol; -begin - if not Assigned(FInstance) then - FInstance := TLocalNotificationProtocol.Create(); - Result := FInstance; -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyTools.Protocol.Local' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Notification protocol implementation for *) +(* local messages *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyTools.Notification.Protocol.Local; + +interface + +uses + System.Types, + System.Classes, + System.SysUtils, + System.Threading, + System.Generics.Collections, + PyTools.Notification.Channel, + PyTools.Notification.Protocol; + +type + TLocalNotificationProtocol = class(TInterfacedObject, IClientNotificationProtocol, IServerNotificationProtocol) + private type + TLocalQueue = class(TQueue); + + TLocalClient = class(TBaseClientNotificationProtocol) + private + [Unsafe] + FParent: TLocalNotificationProtocol; + FTask: TThread; + protected + function TransmitContent(const AContent: TNotificationChannel): IAsyncResult; override; + function ReceiveContent(): IAsyncResult; override; + public + constructor Create(const AParent: TLocalNotificationProtocol); reintroduce; + destructor Destroy(); override; + end; + + TLocalServer = class(TBaseServerNotificationProtocol) + private + [Unsafe] + FParent: TLocalNotificationProtocol; + FTask: TThread; + protected + function TransmitContent(const AContent: TNotificationChannel): IAsyncResult; override; + function ReceiveContent(): IAsyncResult; override; + public + constructor Create(const AParent: TLocalNotificationProtocol); reintroduce; + destructor Destroy(); override; + end; + + TNotificationAsyncResult = class(TBaseAsyncResult) + private + FAsyncTask: TProc; + protected + procedure Schedule; override; + procedure AsyncDispatch; override; + public + constructor Create(const AContext: TObject; const AAsyncTask: TProc); + end; + + TNotificationAsyncResult = class(TNotificationAsyncResult) + private + FRetVal: TResult; + public + function GetRetVal: TResult; + end; + private + // Protocols + FClient: IClientNotificationProtocol; + FServer: IServerNotificationProtocol; + // Shared queues + FClientQueue: TLocalQueue; + FServerQueue: TLocalQueue; + procedure ClearQueue(const AQueue: TLocalQueue); + private + property Client: IClientNotificationProtocol read FClient + implements IClientNotificationProtocol; + property Server: IServerNotificationProtocol read FServer + implements IServerNotificationProtocol; + public + constructor Create(); + destructor Destroy(); override; + + /// + /// Event subscription. + /// + function SubscribeToRequest(const ARequestChannelIdentifier: TRequestChannelIdentifier; + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; + /// + /// Generic event subscription. + /// + function SubscribeToRequest( + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; + /// + /// Event broadcaster. + /// + function BroadcastEvent(const AEvent: TEventChannel): IAwait; + + /// + /// Event subscription. + /// + function SubscribeToEvent(const AEventChannelIdentifier: TEventChannelIdentifier; + const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; + /// + /// Generic event subscription. + /// + function SubscribeToEvent( + const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; + /// + /// Request handler. + /// Use IAwait to guarantee message delivery. + /// + function SendRequest(const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; overload; + /// + /// Generic request handler. + /// Use IAwait to guarantee message delivery. + /// + function SendRequest(const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; overload; + end; + + TAppNotificationProtocol = class + private + class var FInstance: TLocalNotificationProtocol; + class function GetInstance: TLocalNotificationProtocol; static; + public + class destructor Destroy(); + + class property Instance: TLocalNotificationProtocol read GetInstance; + end; + +implementation + +{ TLocalNotificationProtocol.TLocalClient } + +constructor TLocalNotificationProtocol.TLocalClient.Create( + const AParent: TLocalNotificationProtocol); +begin + inherited Create(); + FParent := AParent; + FTask := TThread.CreateAnonymousThread( + procedure() + var + LAsync: IAsyncResult; + LNotification: TNotificationChannel; + begin + while not FTask.CheckTerminated() do begin + if not Enabled then begin + Sleep(100); + Continue; + end; + + try + LAsync := ReceiveContent(); + LNotification := TNotificationAsyncResult(LAsync).GetRetVal(); + if Assigned(LNotification) then begin + try + Process(LNotification); + finally + LNotification.Free(); + end; + end; + except + // + end; + Sleep(100); + end; + TThread.RemoveQueuedEvents(TThread.Current); + end); + FTask.FreeOnTerminate := false; + FTask.Start(); +end; + +destructor TLocalNotificationProtocol.TLocalClient.Destroy; +begin + FTask.Terminate(); + FTask.WaitFor(); + FTask.Free(); + inherited; +end; + +function TLocalNotificationProtocol.TLocalClient.ReceiveContent: IAsyncResult; +var + LAsyncResult: TNotificationAsyncResult; +begin + LAsyncResult := TNotificationAsyncResult.Create(Self, + procedure() + begin + if (FParent.FClientQueue.Count > 0) then + LAsyncResult.FRetVal := FParent.FClientQueue.Dequeue() + else + LAsyncResult.FRetVal := nil; + end); + + Result := LAsyncResult.Invoke(); +end; + +function TLocalNotificationProtocol.TLocalClient.TransmitContent( + const AContent: TNotificationChannel): IAsyncResult; +begin + Result := TNotificationAsyncResult.Create(Self, procedure() begin + FParent.FServerQueue.Enqueue(AContent); + end).Invoke(); +end; + +{ TLocalNotificationProtocol.TLocalServer } + +constructor TLocalNotificationProtocol.TLocalServer.Create( + const AParent: TLocalNotificationProtocol); +begin + inherited Create(); + FParent := AParent; + FTask := TThread.CreateAnonymousThread( + procedure() + var + LAsync: IAsyncResult; + LNotification: TNotificationChannel; + begin + while not FTask.CheckTerminated() do begin + if not Enabled then begin + Sleep(100); + Continue; + end; + + LAsync := ReceiveContent(); + LNotification := TNotificationAsyncResult(LAsync).GetRetVal(); + if Assigned(LNotification) then begin + try + Process(LNotification); + finally + LNotification.Free(); + end; + end; + Sleep(100); + end; + TThread.RemoveQueuedEvents(TThread.Current); + end); + FTask.FreeOnTerminate := false; + FTask.Start(); +end; + +destructor TLocalNotificationProtocol.TLocalServer.Destroy; +begin + FTask.Terminate(); + FTask.WaitFor(); + FTask.Free(); + inherited; +end; + +function TLocalNotificationProtocol.TLocalServer.ReceiveContent: IAsyncResult; +var + LAsyncResult: TNotificationAsyncResult; +begin + LAsyncResult := TNotificationAsyncResult.Create(Self, + procedure() + begin + if (FParent.FServerQueue.Count > 0) then + LAsyncResult.FRetVal := FParent.FServerQueue.Dequeue + else + LAsyncResult.FRetVal := nil; + end); + + Result := LAsyncResult.Invoke(); +end; + +function TLocalNotificationProtocol.TLocalServer.TransmitContent( + const AContent: TNotificationChannel): IAsyncResult; +begin + Result := TNotificationAsyncResult.Create(Self, procedure() begin + FParent.FClientQueue.Enqueue(AContent); + end).Invoke(); +end; + +{ TLocalNotificationProtocol.TNotificationAsyncResult } + +constructor TLocalNotificationProtocol.TNotificationAsyncResult.Create( + const AContext: TObject; const AAsyncTask: TProc); +begin + inherited Create(AContext); + FAsyncTask := AAsyncTask; +end; + +procedure TLocalNotificationProtocol.TNotificationAsyncResult.Schedule; +begin + TThread.ForceQueue(TThread.Current, DoAsyncDispatch); +end; + +procedure TLocalNotificationProtocol.TNotificationAsyncResult.AsyncDispatch; +begin + FAsyncTask(); +end; + +{ TLocalNotificationProtocol.TNotificationAsyncResult } + +function TLocalNotificationProtocol.TNotificationAsyncResult.GetRetVal: TResult; +begin + WaitForCompletion; + Result := FRetVal; +end; + +{ TLocalNotificationProtocol } + +procedure TLocalNotificationProtocol.ClearQueue(const AQueue: TLocalQueue); +begin + while (AQueue.Count > 0) do + AQueue.Dequeue().Free(); +end; + +constructor TLocalNotificationProtocol.Create; +begin + inherited; + FClientQueue := TLocalQueue.Create(); + FServerQueue := TLocalQueue.Create(); + FClient := TLocalClient.Create(Self); + FServer := TLocalServer.Create(Self); +end; + +destructor TLocalNotificationProtocol.Destroy; +begin + FServer := nil; + FClient := nil; + ClearQueue(FServerQueue); + ClearQueue(FClientQueue); + FServerQueue.Free(); + FClientQueue.Free(); + inherited; +end; + +function TLocalNotificationProtocol.BroadcastEvent( + const AEvent: TEventChannel): IAwait; +begin + Result := (FServer as TBaseServerNotificationProtocol).BroadcastEvent(AEvent); +end; + +function TLocalNotificationProtocol.SubscribeToEvent( + const AEventChannelIdentifier: TEventChannelIdentifier; + const AEventNotification: TEventNotificationCallback): IDisconnectable; +begin + Result := (FClient as TBaseClientNotificationProtocol).SubscribeToEvent( + AEventChannelIdentifier, AEventNotification); +end; + +function TLocalNotificationProtocol.SubscribeToEvent( + const AEventNotification: TEventNotificationCallback): IDisconnectable; +begin + Result := (FClient as TBaseClientNotificationProtocol).SubscribeToEvent( + AEventNotification); +end; + +function TLocalNotificationProtocol.SendRequest(const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; +begin + Result := (FClient as TBaseClientNotificationProtocol).SendRequest(ARequest, + AAccept, AReject); +end; + +function TLocalNotificationProtocol.SendRequest( + const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; +begin + Result := (FClient as TBaseClientNotificationProtocol).SendRequest( + ARequest, AAccept, AReject); +end; + +function TLocalNotificationProtocol.SubscribeToRequest( + const ARequestChannelIdentifier: TRequestChannelIdentifier; + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; +begin + Result := (FServer as TBaseServerNotificationProtocol).SubscribeToRequest( + ARequestChannelIdentifier, ARequestNotification); +end; + +function TLocalNotificationProtocol.SubscribeToRequest( + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; +begin + Result := (FServer as TBaseServerNotificationProtocol).SubscribeToRequest( + ARequestNotification); +end; + +{ TAppNotificationProtocol } + +class destructor TAppNotificationProtocol.Destroy; +begin + FInstance.Free(); +end; + +class function TAppNotificationProtocol.GetInstance: TLocalNotificationProtocol; +begin + if not Assigned(FInstance) then + FInstance := TLocalNotificationProtocol.Create(); + Result := FInstance; +end; + +end. diff --git a/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.pas b/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.pas index 2041ab8..71c0cc5 100644 --- a/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.pas +++ b/src/Tools/Notification/Protocol/PyTools.Notification.Protocol.pas @@ -1,523 +1,523 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyTools.Protocol.Local' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Abstract notification protocol implementation *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyTools.Notification.Protocol; - -interface - -uses - System.Types, - System.SysUtils, - System.Classes, - System.SyncObjs, - System.Threading, - System.Generics.Collections, - PyTools.Notification.Channel; - -type - IDisconnectable = interface - ['{21B1CCF8-D844-4738-BB3E-31C826B33949}'] - procedure Disconnect(); - end; - - IAwait = interface - ['{AF7099CE-BBBE-43EF-AD18-78FEA86BFCC2}'] - function Wait(const ATimeOut: cardinal): boolean; - end; - - IClientNotificationProtocol = interface - ['{AAF24EDA-B2E3-47F1-93E9-6C3B112A9EBB}'] - /// - /// Event subscription. - /// - function SubscribeToEvent(const AEventChannelIdentifier: TEventChannelIdentifier; - const AEventNotification: TEventNotificationCallback): IDisconnectable; - /// - /// Request handler. - /// Use IAwait to guarantee message delivery. - /// - function SendRequest(const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; - end; - - IServerNotificationProtocol = interface - ['{936441AE-95B1-4952-BA67-DAD15EAFBE7B}'] - /// - /// Event subscription. - /// - function SubscribeToRequest(const ARequestChannelIdentifier: TRequestChannelIdentifier; - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; - /// - /// Event broadcaster. - /// - function BroadcastEvent(const AEvent: TEventChannel): IAwait; - /// - /// Response handler. - /// Use IAwait to guarantee message delivery. - /// - function SendResponse(const AResponse: TResponseChannel): IAwait; - end; - - TBaseNotificationProtocol = class(TInterfacedObject) - strict private - FEnabled: boolean; - protected type - TAwait = class(TInterfacedObject, IAwait) - private - FAsyncResult: IAsyncResult; - public - constructor Create(const AAsyncResult: IAsyncResult); - function Wait(const ATimeOut: cardinal): boolean; - end; - TDisconnectable = class(TInterfacedObject, IDisconnectable) - private type - TOnDisconnect = reference to procedure(); - private - FOnDisconnect: TOnDisconnect; - public - constructor Create(const AOnDisconnect: TOnDisconnect); - - procedure Disconnect(); - end; - protected - function TransmitContent(const AContent: TNotificationChannel): IAsyncResult; virtual; abstract; - function ReceiveContent(): IAsyncResult; virtual; abstract; - - procedure Process(const AContent: TNotificationChannel); - procedure ProcessEvent(const AEvent: TEventChannel); virtual; abstract; - procedure ProcessRequest(const ARequest: TRequestChannel); virtual; abstract; - procedure ProcessResponse(const AResponse: TResponseChannel); virtual; abstract; - - // Helpers - procedure SafeAccess(const AInstance: TObject; const AProc: TProc); - public - constructor Create(); virtual; - - property Enabled: boolean read FEnabled write FEnabled; - end; - - TBaseClientNotificationProtocol = class(TBaseNotificationProtocol, IClientNotificationProtocol) - private type - TRequestCallbackMethods = TPair< - TRequestNotificationAcceptCallback, - TRequestNotificationRejectCallback>; - TIdentifiedEventCallbacks = TThreadList; - private - FEventCallbacks: TObjectDictionary; - FRequestCallbacks: TDictionary; - protected - procedure ProcessEvent(const AEvent: TEventChannel); override; - procedure ProcessRequest(const ARequest: TRequestChannel); override; - procedure ProcessResponse(const AResponse: TResponseChannel); override; - public - constructor Create(); override; - destructor Destroy(); override; - /// - /// Event subscription. - /// - function SubscribeToEvent(const AEventChannelIdentifier: TEventChannelIdentifier; - const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; - /// - /// Generic event subscription. - /// - function SubscribeToEvent( - const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; - /// - /// Request handler. - /// Use IAwait to guarantee message delivery. - /// - function SendRequest(const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; overload; - /// - /// Generic request handler. - /// Use IAwait to guarantee message delivery. - /// - function SendRequest(const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; overload; - end; - - TBaseServerNotificationProtocol = class(TBaseNotificationProtocol, IServerNotificationProtocol) - private - FRequestCallbacks: TDictionary; - protected - procedure ProcessEvent(const AEvent: TEventChannel); override; - procedure ProcessRequest(const ARequest: TRequestChannel); override; - procedure ProcessResponse(const AResponse: TResponseChannel); override; - function SendResponse(const AResponse: TResponseChannel): IAwait; - public - constructor Create(); override; - destructor Destroy(); override; - /// - /// Event subscription. - /// - function SubscribeToRequest(const ARequestChannelIdentifier: TRequestChannelIdentifier; - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; - /// - /// Generic event subscription. - /// - function SubscribeToRequest( - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; - /// - /// Event broadcaster. - /// - function BroadcastEvent(const AEvent: TEventChannel): IAwait; - end; - -implementation - -{ TBaseNotificationProtocol.TAwait } - -constructor TBaseNotificationProtocol.TAwait.Create( - const AAsyncResult: IAsyncResult); -begin - inherited Create(); - FAsyncResult := AAsyncResult; -end; - -function TBaseNotificationProtocol.TAwait.Wait(const ATimeOut: cardinal): boolean; -begin - Result := FAsyncResult.AsyncWaitEvent.WaitFor(ATimeOut) = TWaitResult.wrSignaled; - if Result then - TBaseAsyncResult(FAsyncResult).WaitForCompletion(); -end; - -{ TBaseNotificationProtocol.TDisconnectable } - -constructor TBaseNotificationProtocol.TDisconnectable.Create( - const AOnDisconnect: TOnDisconnect); -begin - inherited Create(); - FOnDisconnect := AOnDisconnect; -end; - -procedure TBaseNotificationProtocol.TDisconnectable.Disconnect; -begin - if Assigned(FOnDisconnect) then - FOnDisconnect(); - FOnDisconnect := nil; -end; - -{ TBaseNotificationProtocol } - -constructor TBaseNotificationProtocol.Create; -begin - inherited Create(); - FEnabled := true; -end; - -procedure TBaseNotificationProtocol.Process( - const AContent: TNotificationChannel); -begin - case AContent.ChannelType of - TNotificationChannelIdentifier.Event: - ProcessEvent(AContent as TEventChannel); - TNotificationChannelIdentifier.Request: - ProcessRequest(AContent as TRequestChannel); - TNotificationChannelIdentifier.Response: - ProcessResponse(AContent as TResponseChannel); - end; -end; - -procedure TBaseNotificationProtocol.SafeAccess(const AInstance: TObject; - const AProc: TProc); -begin - Assert(Assigned(AProc), 'Invalid argument "AProc".'); - - if not Assigned(AInstance) then - Exit; - - TMonitor.Enter(AInstance); - try - AProc(); - TMonitor.PulseAll(AInstance); - finally - TMonitor.Exit(AInstance); - end; -end; - -{ TBaseClientNotificationProtocol } - -constructor TBaseClientNotificationProtocol.Create; -begin - inherited; - FEventCallbacks := TObjectDictionary< - TEventChannelIdentifier, - TIdentifiedEventCallbacks>.Create([doOwnsValues]); - FRequestCallbacks := TDictionary.Create(); -end; - -destructor TBaseClientNotificationProtocol.Destroy; -begin - FRequestCallbacks.Free(); - FEventCallbacks.Free(); - inherited; -end; - -procedure TBaseClientNotificationProtocol.ProcessEvent( - const AEvent: TEventChannel); -var - LCallbacks: TIdentifiedEventCallbacks; - LIdentifierCallbacks: TList; - I: Integer; -begin - SafeAccess(FEventCallbacks, procedure() - begin - LCallbacks := FEventCallbacks[AEvent.EventType]; - end); - - if Assigned(LCallbacks) then begin - LIdentifierCallbacks := LCallbacks.LockList(); - try - // Broadcasting events inside this critical section avoids - // notification for unsubscribing peers - for I := 0 to LIdentifierCallbacks.Count - 1 do begin - try - LIdentifierCallbacks[I](AEvent); - except - // - end; - end; - finally - LCallbacks.UnlockList(); - end; - end; -end; - -procedure TBaseClientNotificationProtocol.ProcessRequest( - const ARequest: TRequestChannel); -begin - Assert(false, 'Unable to process request in the client side.'); -end; - -procedure TBaseClientNotificationProtocol.ProcessResponse( - const AResponse: TResponseChannel); -var - LCallbacks: TRequestCallbackMethods; -begin - SafeAccess(FRequestCallbacks, procedure() begin - if FRequestCallbacks.ContainsKey(AResponse.Id) then begin - try - LCallbacks := FRequestCallbacks[AResponse.Id]; - - if AResponse.InheritsFrom(TErrorResponseChannel) then - LCallbacks.Value(TErrorResponseChannel(AResponse)) - else - LCallbacks.Key(AResponse); - finally - FRequestCallbacks.Remove(AResponse.Id); - end; - end; - end); -end; - -function TBaseClientNotificationProtocol.SubscribeToEvent( - const AEventChannelIdentifier: TEventChannelIdentifier; - const AEventNotification: TEventNotificationCallback): IDisconnectable; -var - LCallbacks: TIdentifiedEventCallbacks; - LIdentifierCallbacks: TList; -begin - Assert(not AEventChannelIdentifier.Trim().IsEmpty(), 'Invalid argument "AEventChannelIdentifier".'); - - if not Enabled then - Exit(nil); - - SafeAccess(FEventCallbacks, procedure() begin - if not FEventCallbacks.ContainsKey(AEventChannelIdentifier) then - FEventCallbacks.Add( - AEventChannelIdentifier, - TIdentifiedEventCallbacks.Create()); - - LCallbacks := FEventCallbacks[AEventChannelIdentifier]; - end); - - if Assigned(LCallbacks) then begin - LIdentifierCallbacks := LCallbacks.LockList(); - try - LIdentifierCallbacks.Add(AEventNotification); - finally - LCallbacks.UnlockList(); - end; - end; - - Result := TDisconnectable.Create(procedure() begin - SafeAccess(FEventCallbacks, procedure() begin - LCallbacks := FEventCallbacks[AEventChannelIdentifier]; - end); - - if Assigned(LCallbacks) then - LIdentifierCallbacks := LCallbacks.LockList(); - try - LIdentifierCallbacks.Remove(AEventNotification); - finally - LCallbacks.UnlockList(); - end; - end); -end; - -function TBaseClientNotificationProtocol.SubscribeToEvent( - const AEventNotification: TEventNotificationCallback): IDisconnectable; -begin - Result := SubscribeToEvent(TEvent.GetEventChannelType(), - procedure(const Arg: TEventChannel) - begin - if (Arg is TEvent) then - AEventNotification(Arg); - end); -end; - -function TBaseClientNotificationProtocol.SendRequest( - const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; -begin - Assert(Assigned(ARequest), 'Invalid argument "ARequest".'); - - if not Enabled then - Exit(nil); - - SafeAccess(FEventCallbacks, procedure() begin - FRequestCallbacks.Add( - ARequest.Id, - TRequestCallbackMethods.Create(AAccept, AReject)); - end); - - Result := TAwait.Create(TransmitContent(ARequest)); -end; - -function TBaseClientNotificationProtocol.SendRequest( - const ARequest: TRequestChannel; - const AAccept: TRequestNotificationAcceptCallback; - const AReject: TRequestNotificationRejectCallback): IAwait; -begin - Result := SendRequest(ARequest, - procedure(const Arg: TResponseChannel) - begin - AAccept(Arg as TResponse); - end, AReject); -end; - -{ TBaseServerNotificationProtocol } - -constructor TBaseServerNotificationProtocol.Create; -begin - inherited Create(); - FRequestCallbacks := TDictionary.Create(); -end; - -destructor TBaseServerNotificationProtocol.Destroy; -begin - FRequestCallbacks.Free(); - inherited; -end; - -procedure TBaseServerNotificationProtocol.ProcessEvent( - const AEvent: TEventChannel); -begin - Assert(false, 'Unable to process event in the server side.'); -end; - -procedure TBaseServerNotificationProtocol.ProcessRequest( - const ARequest: TRequestChannel); -var - LCallback: TRequestNotificationCallback; - LResponse: TResponseChannel; -begin - TMonitor.Enter(FRequestCallbacks); - try - LCallback := FRequestCallbacks[ARequest.RequestType]; - TMonitor.PulseAll(FRequestCallbacks); - finally - TMonitor.Exit(FRequestCallbacks); - end; - - if Assigned(LCallback) then begin - LResponse := LCallback(ARequest); - LResponse.Id := ARequest.Id; - SendResponse(LResponse); - end; -end; - -procedure TBaseServerNotificationProtocol.ProcessResponse( - const AResponse: TResponseChannel); -begin - Assert(false, 'Unable to process response in the server side.'); -end; - -function TBaseServerNotificationProtocol.BroadcastEvent( - const AEvent: TEventChannel): IAwait; -begin - Assert(Assigned(AEvent), 'Invalid argument "AEvent".'); - - Result := TAwait.Create(TransmitContent(AEvent)); -end; - -function TBaseServerNotificationProtocol.SendResponse( - const AResponse: TResponseChannel): IAwait; -begin - Result := TAwait.Create(TransmitContent(AResponse)); -end; - -function TBaseServerNotificationProtocol.SubscribeToRequest( - const ARequestChannelIdentifier: TRequestChannelIdentifier; - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; -begin - Assert(not ARequestChannelIdentifier.Trim().IsEmpty(), 'Invalid argument "ARequestChannelIdentifier".'); - - if not Enabled then - Exit(nil); - - SafeAccess(FRequestCallbacks, procedure() begin - FRequestCallbacks.Add(ARequestChannelIdentifier, ARequestNotification); - end); - - Result := TDisconnectable.Create(procedure() begin - SafeAccess(FRequestCallbacks, procedure() begin - FRequestCallbacks.Remove(ARequestChannelIdentifier); - end); - end); -end; - -function TBaseServerNotificationProtocol.SubscribeToRequest( - const ARequestNotification: TRequestNotificationCallback): IDisconnectable; -begin - Result := SubscribeToRequest(TRequest.GetIdentifier(), - function(const Arg: TRequestChannel): TResponseChannel begin - if (Arg is TRequest) then - Result := ARequestNotification(Arg) - else - Result := nil; - end); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyTools.Protocol.Local' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Abstract notification protocol implementation *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyTools.Notification.Protocol; + +interface + +uses + System.Types, + System.SysUtils, + System.Classes, + System.SyncObjs, + System.Threading, + System.Generics.Collections, + PyTools.Notification.Channel; + +type + IDisconnectable = interface + ['{21B1CCF8-D844-4738-BB3E-31C826B33949}'] + procedure Disconnect(); + end; + + IAwait = interface + ['{AF7099CE-BBBE-43EF-AD18-78FEA86BFCC2}'] + function Wait(const ATimeOut: cardinal): boolean; + end; + + IClientNotificationProtocol = interface + ['{AAF24EDA-B2E3-47F1-93E9-6C3B112A9EBB}'] + /// + /// Event subscription. + /// + function SubscribeToEvent(const AEventChannelIdentifier: TEventChannelIdentifier; + const AEventNotification: TEventNotificationCallback): IDisconnectable; + /// + /// Request handler. + /// Use IAwait to guarantee message delivery. + /// + function SendRequest(const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; + end; + + IServerNotificationProtocol = interface + ['{936441AE-95B1-4952-BA67-DAD15EAFBE7B}'] + /// + /// Event subscription. + /// + function SubscribeToRequest(const ARequestChannelIdentifier: TRequestChannelIdentifier; + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; + /// + /// Event broadcaster. + /// + function BroadcastEvent(const AEvent: TEventChannel): IAwait; + /// + /// Response handler. + /// Use IAwait to guarantee message delivery. + /// + function SendResponse(const AResponse: TResponseChannel): IAwait; + end; + + TBaseNotificationProtocol = class(TInterfacedObject) + strict private + FEnabled: boolean; + protected type + TAwait = class(TInterfacedObject, IAwait) + private + FAsyncResult: IAsyncResult; + public + constructor Create(const AAsyncResult: IAsyncResult); + function Wait(const ATimeOut: cardinal): boolean; + end; + TDisconnectable = class(TInterfacedObject, IDisconnectable) + private type + TOnDisconnect = reference to procedure(); + private + FOnDisconnect: TOnDisconnect; + public + constructor Create(const AOnDisconnect: TOnDisconnect); + + procedure Disconnect(); + end; + protected + function TransmitContent(const AContent: TNotificationChannel): IAsyncResult; virtual; abstract; + function ReceiveContent(): IAsyncResult; virtual; abstract; + + procedure Process(const AContent: TNotificationChannel); + procedure ProcessEvent(const AEvent: TEventChannel); virtual; abstract; + procedure ProcessRequest(const ARequest: TRequestChannel); virtual; abstract; + procedure ProcessResponse(const AResponse: TResponseChannel); virtual; abstract; + + // Helpers + procedure SafeAccess(const AInstance: TObject; const AProc: TProc); + public + constructor Create(); virtual; + + property Enabled: boolean read FEnabled write FEnabled; + end; + + TBaseClientNotificationProtocol = class(TBaseNotificationProtocol, IClientNotificationProtocol) + private type + TRequestCallbackMethods = TPair< + TRequestNotificationAcceptCallback, + TRequestNotificationRejectCallback>; + TIdentifiedEventCallbacks = TThreadList; + private + FEventCallbacks: TObjectDictionary; + FRequestCallbacks: TDictionary; + protected + procedure ProcessEvent(const AEvent: TEventChannel); override; + procedure ProcessRequest(const ARequest: TRequestChannel); override; + procedure ProcessResponse(const AResponse: TResponseChannel); override; + public + constructor Create(); override; + destructor Destroy(); override; + /// + /// Event subscription. + /// + function SubscribeToEvent(const AEventChannelIdentifier: TEventChannelIdentifier; + const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; + /// + /// Generic event subscription. + /// + function SubscribeToEvent( + const AEventNotification: TEventNotificationCallback): IDisconnectable; overload; + /// + /// Request handler. + /// Use IAwait to guarantee message delivery. + /// + function SendRequest(const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; overload; + /// + /// Generic request handler. + /// Use IAwait to guarantee message delivery. + /// + function SendRequest(const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; overload; + end; + + TBaseServerNotificationProtocol = class(TBaseNotificationProtocol, IServerNotificationProtocol) + private + FRequestCallbacks: TDictionary; + protected + procedure ProcessEvent(const AEvent: TEventChannel); override; + procedure ProcessRequest(const ARequest: TRequestChannel); override; + procedure ProcessResponse(const AResponse: TResponseChannel); override; + function SendResponse(const AResponse: TResponseChannel): IAwait; + public + constructor Create(); override; + destructor Destroy(); override; + /// + /// Event subscription. + /// + function SubscribeToRequest(const ARequestChannelIdentifier: TRequestChannelIdentifier; + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; + /// + /// Generic event subscription. + /// + function SubscribeToRequest( + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; overload; + /// + /// Event broadcaster. + /// + function BroadcastEvent(const AEvent: TEventChannel): IAwait; + end; + +implementation + +{ TBaseNotificationProtocol.TAwait } + +constructor TBaseNotificationProtocol.TAwait.Create( + const AAsyncResult: IAsyncResult); +begin + inherited Create(); + FAsyncResult := AAsyncResult; +end; + +function TBaseNotificationProtocol.TAwait.Wait(const ATimeOut: cardinal): boolean; +begin + Result := FAsyncResult.AsyncWaitEvent.WaitFor(ATimeOut) = TWaitResult.wrSignaled; + if Result then + TBaseAsyncResult(FAsyncResult).WaitForCompletion(); +end; + +{ TBaseNotificationProtocol.TDisconnectable } + +constructor TBaseNotificationProtocol.TDisconnectable.Create( + const AOnDisconnect: TOnDisconnect); +begin + inherited Create(); + FOnDisconnect := AOnDisconnect; +end; + +procedure TBaseNotificationProtocol.TDisconnectable.Disconnect; +begin + if Assigned(FOnDisconnect) then + FOnDisconnect(); + FOnDisconnect := nil; +end; + +{ TBaseNotificationProtocol } + +constructor TBaseNotificationProtocol.Create; +begin + inherited Create(); + FEnabled := true; +end; + +procedure TBaseNotificationProtocol.Process( + const AContent: TNotificationChannel); +begin + case AContent.ChannelType of + TNotificationChannelIdentifier.Event: + ProcessEvent(AContent as TEventChannel); + TNotificationChannelIdentifier.Request: + ProcessRequest(AContent as TRequestChannel); + TNotificationChannelIdentifier.Response: + ProcessResponse(AContent as TResponseChannel); + end; +end; + +procedure TBaseNotificationProtocol.SafeAccess(const AInstance: TObject; + const AProc: TProc); +begin + Assert(Assigned(AProc), 'Invalid argument "AProc".'); + + if not Assigned(AInstance) then + Exit; + + TMonitor.Enter(AInstance); + try + AProc(); + TMonitor.PulseAll(AInstance); + finally + TMonitor.Exit(AInstance); + end; +end; + +{ TBaseClientNotificationProtocol } + +constructor TBaseClientNotificationProtocol.Create; +begin + inherited; + FEventCallbacks := TObjectDictionary< + TEventChannelIdentifier, + TIdentifiedEventCallbacks>.Create([doOwnsValues]); + FRequestCallbacks := TDictionary.Create(); +end; + +destructor TBaseClientNotificationProtocol.Destroy; +begin + FRequestCallbacks.Free(); + FEventCallbacks.Free(); + inherited; +end; + +procedure TBaseClientNotificationProtocol.ProcessEvent( + const AEvent: TEventChannel); +var + LCallbacks: TIdentifiedEventCallbacks; + LIdentifierCallbacks: TList; + I: Integer; +begin + SafeAccess(FEventCallbacks, procedure() + begin + LCallbacks := FEventCallbacks[AEvent.EventType]; + end); + + if Assigned(LCallbacks) then begin + LIdentifierCallbacks := LCallbacks.LockList(); + try + // Broadcasting events inside this critical section avoids + // notification for unsubscribing peers + for I := 0 to LIdentifierCallbacks.Count - 1 do begin + try + LIdentifierCallbacks[I](AEvent); + except + // + end; + end; + finally + LCallbacks.UnlockList(); + end; + end; +end; + +procedure TBaseClientNotificationProtocol.ProcessRequest( + const ARequest: TRequestChannel); +begin + Assert(false, 'Unable to process request in the client side.'); +end; + +procedure TBaseClientNotificationProtocol.ProcessResponse( + const AResponse: TResponseChannel); +var + LCallbacks: TRequestCallbackMethods; +begin + SafeAccess(FRequestCallbacks, procedure() begin + if FRequestCallbacks.ContainsKey(AResponse.Id) then begin + try + LCallbacks := FRequestCallbacks[AResponse.Id]; + + if AResponse.InheritsFrom(TErrorResponseChannel) then + LCallbacks.Value(TErrorResponseChannel(AResponse)) + else + LCallbacks.Key(AResponse); + finally + FRequestCallbacks.Remove(AResponse.Id); + end; + end; + end); +end; + +function TBaseClientNotificationProtocol.SubscribeToEvent( + const AEventChannelIdentifier: TEventChannelIdentifier; + const AEventNotification: TEventNotificationCallback): IDisconnectable; +var + LCallbacks: TIdentifiedEventCallbacks; + LIdentifierCallbacks: TList; +begin + Assert(not AEventChannelIdentifier.Trim().IsEmpty(), 'Invalid argument "AEventChannelIdentifier".'); + + if not Enabled then + Exit(nil); + + SafeAccess(FEventCallbacks, procedure() begin + if not FEventCallbacks.ContainsKey(AEventChannelIdentifier) then + FEventCallbacks.Add( + AEventChannelIdentifier, + TIdentifiedEventCallbacks.Create()); + + LCallbacks := FEventCallbacks[AEventChannelIdentifier]; + end); + + if Assigned(LCallbacks) then begin + LIdentifierCallbacks := LCallbacks.LockList(); + try + LIdentifierCallbacks.Add(AEventNotification); + finally + LCallbacks.UnlockList(); + end; + end; + + Result := TDisconnectable.Create(procedure() begin + SafeAccess(FEventCallbacks, procedure() begin + LCallbacks := FEventCallbacks[AEventChannelIdentifier]; + end); + + if Assigned(LCallbacks) then + LIdentifierCallbacks := LCallbacks.LockList(); + try + LIdentifierCallbacks.Remove(AEventNotification); + finally + LCallbacks.UnlockList(); + end; + end); +end; + +function TBaseClientNotificationProtocol.SubscribeToEvent( + const AEventNotification: TEventNotificationCallback): IDisconnectable; +begin + Result := SubscribeToEvent(TEvent.GetEventChannelType(), + procedure(const Arg: TEventChannel) + begin + if (Arg is TEvent) then + AEventNotification(Arg); + end); +end; + +function TBaseClientNotificationProtocol.SendRequest( + const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; +begin + Assert(Assigned(ARequest), 'Invalid argument "ARequest".'); + + if not Enabled then + Exit(nil); + + SafeAccess(FEventCallbacks, procedure() begin + FRequestCallbacks.Add( + ARequest.Id, + TRequestCallbackMethods.Create(AAccept, AReject)); + end); + + Result := TAwait.Create(TransmitContent(ARequest)); +end; + +function TBaseClientNotificationProtocol.SendRequest( + const ARequest: TRequestChannel; + const AAccept: TRequestNotificationAcceptCallback; + const AReject: TRequestNotificationRejectCallback): IAwait; +begin + Result := SendRequest(ARequest, + procedure(const Arg: TResponseChannel) + begin + AAccept(Arg as TResponse); + end, AReject); +end; + +{ TBaseServerNotificationProtocol } + +constructor TBaseServerNotificationProtocol.Create; +begin + inherited Create(); + FRequestCallbacks := TDictionary.Create(); +end; + +destructor TBaseServerNotificationProtocol.Destroy; +begin + FRequestCallbacks.Free(); + inherited; +end; + +procedure TBaseServerNotificationProtocol.ProcessEvent( + const AEvent: TEventChannel); +begin + Assert(false, 'Unable to process event in the server side.'); +end; + +procedure TBaseServerNotificationProtocol.ProcessRequest( + const ARequest: TRequestChannel); +var + LCallback: TRequestNotificationCallback; + LResponse: TResponseChannel; +begin + TMonitor.Enter(FRequestCallbacks); + try + LCallback := FRequestCallbacks[ARequest.RequestType]; + TMonitor.PulseAll(FRequestCallbacks); + finally + TMonitor.Exit(FRequestCallbacks); + end; + + if Assigned(LCallback) then begin + LResponse := LCallback(ARequest); + LResponse.Id := ARequest.Id; + SendResponse(LResponse); + end; +end; + +procedure TBaseServerNotificationProtocol.ProcessResponse( + const AResponse: TResponseChannel); +begin + Assert(false, 'Unable to process response in the server side.'); +end; + +function TBaseServerNotificationProtocol.BroadcastEvent( + const AEvent: TEventChannel): IAwait; +begin + Assert(Assigned(AEvent), 'Invalid argument "AEvent".'); + + Result := TAwait.Create(TransmitContent(AEvent)); +end; + +function TBaseServerNotificationProtocol.SendResponse( + const AResponse: TResponseChannel): IAwait; +begin + Result := TAwait.Create(TransmitContent(AResponse)); +end; + +function TBaseServerNotificationProtocol.SubscribeToRequest( + const ARequestChannelIdentifier: TRequestChannelIdentifier; + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; +begin + Assert(not ARequestChannelIdentifier.Trim().IsEmpty(), 'Invalid argument "ARequestChannelIdentifier".'); + + if not Enabled then + Exit(nil); + + SafeAccess(FRequestCallbacks, procedure() begin + FRequestCallbacks.Add(ARequestChannelIdentifier, ARequestNotification); + end); + + Result := TDisconnectable.Create(procedure() begin + SafeAccess(FRequestCallbacks, procedure() begin + FRequestCallbacks.Remove(ARequestChannelIdentifier); + end); + end); +end; + +function TBaseServerNotificationProtocol.SubscribeToRequest( + const ARequestNotification: TRequestNotificationCallback): IDisconnectable; +begin + Result := SubscribeToRequest(TRequest.GetIdentifier(), + function(const Arg: TRequestChannel): TResponseChannel begin + if (Arg is TRequest) then + Result := ARequestNotification(Arg) + else + Result := nil; + end); +end; + +end. diff --git a/src/Tools/Notification/PyTools.Notification.Channel.pas b/src/Tools/Notification/PyTools.Notification.Channel.pas index 93c6f43..53fc77e 100644 --- a/src/Tools/Notification/PyTools.Notification.Channel.pas +++ b/src/Tools/Notification/PyTools.Notification.Channel.pas @@ -1,452 +1,452 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyTools.Protocol.Local' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Notification channels, channel identifiers *) -(* and contents *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyTools.Notification.Channel; - -interface - -uses - System.SysUtils, - System.SyncObjs; - -type - TRequestChannel = class; - TResponseChannel = class; - TErrorResponseChannel = class; - TEventChannel = class; - - TRequestNotificationAcceptCallback = reference to procedure(const Arg: T); - TRequestNotificationRejectCallback = reference to procedure(const Arg: TErrorResponseChannel); - - TEventNotificationCallback = reference to procedure(const Arg: T); - TEventNotificationCallback = TEventNotificationCallback; - - TRequestNotificationCallback = reference to function(const Arg: TRequest): TResponseChannel; - TRequestNotificationCallback = TRequestNotificationCallback; - - {$SCOPEDENUMS ON} - TNotificationChannelIdentifier = (Event, Request, Response); - {$SCOPEDENUMS OFF} - - TRequestChannelIdentifier = string; - TEventChannelIdentifier = string; - - TEmptyBody = class - end; - - TEmptyArgument = class - end; - - TErrorResponseBody = class - private - FCode: integer; - FMessage: string; - public - property Code: integer read FCode write FCode; - property Message: string read FMessage write FMessage; - end; - - ChannelTypeAttribute = class(TCustomAttribute) - private - FChannelType: TNotificationChannelIdentifier; - public - constructor Create(const AChannelType: TNotificationChannelIdentifier); - - property ChannelType: TNotificationChannelIdentifier read FChannelType; - end; - - RequestChannelAttribute = class(TCustomAttribute) - private - FIdentifier: TRequestChannelIdentifier; - public - constructor Create(const AIdentifier: TRequestChannelIdentifier); - - property Identifier: TRequestChannelIdentifier read FIdentifier; - end; - - EventTypeAttribute = class(TCustomAttribute) - private - FEventType: TEventChannelIdentifier; - public - constructor Create(const AEventType: TEventChannelIdentifier); - - property EventType: TEventChannelIdentifier read FEventType; - end; - - TNotificationId = integer; - - TNotificationChannel = class - private - class var FNextId: TNotificationId; - private - FChannelType: TNotificationChannelIdentifier; - FId: TNotificationId; - protected - procedure GenerateId(); - protected - class function InitializeType(): T; - class procedure FinalizeType(var AValue); - public - constructor Create(); - class constructor Create(); - - class function GetNotificationChannelType(): TNotificationChannelIdentifier; - - property ChannelType: TNotificationChannelIdentifier read FChannelType; - property Id: TNotificationId read FId; - end; - - [ChannelType(TNotificationChannelIdentifier.Request)] - TRequestChannel = class(TNotificationChannel) - private - FRequestType: TRequestChannelIdentifier; - public - class function GetIdentifier(): TRequestChannelIdentifier; - public - constructor Create(); - - property RequestType: TRequestChannelIdentifier read FRequestType; - end; - - TRequestChannel = class(TRequestChannel) - private - FArguments: TArguments; - public - constructor Create(); - destructor Destroy(); override; - - property Arguments: TArguments read FArguments write FArguments; - end; - - [ChannelType(TNotificationChannelIdentifier.Response)] - TResponseChannel = class(TNotificationChannel) - private - function GetId: TNotificationId; - procedure SetId(const Value: TNotificationId); - public - constructor Create(); - destructor Destroy(); override; - - property Id: TNotificationId read GetId write SetId; - end; - - TResponseChannel = class(TResponseChannel) - private - FBody: TBody; - public - constructor Create(); - destructor Destroy(); override; - - property Body: TBody read FBody; - end; - - TErrorResponseChannel = class(TResponseChannel); - - TErrorResponseChannel = class(TErrorResponseChannel) - private - FBody: TBody; - public - constructor Create(); overload; - destructor Destroy(); override; - - property Body: TBody read FBody; - end; - - [ChannelType(TNotificationChannelIdentifier.Event)] - TEventChannel = class(TNotificationChannel) - private - FEventType: TEventChannelIdentifier; - public - class function GetEventChannelType(): TEventChannelIdentifier; - public - constructor Create(); - - property EventType: TEventChannelIdentifier read FEventType; - end; - - TEventChannel = class(TEventChannel) - private - FBody: TBody; - public - constructor Create(); - destructor Destroy(); override; - - property Body: TBody read FBody; - end; - -implementation - -uses - System.Rtti, - PyTools.Exception; - -{$IFDEF VER340} -type - //For backward compatibility - TCustomAttributeClass = class of TCustomAttribute; - - TRttiObjectHelper = class helper for TRttiObject - public - function GetAttribute(AAttrClass: TCustomAttributeClass): TCustomAttribute; overload; - function GetAttribute: T; overload; inline; - end; -{$ENDIF VER340} - -{$IFDEF VER340} -{ TRttiObjectHelper } - -function TRttiObjectHelper.GetAttribute( - AAttrClass: TCustomAttributeClass): TCustomAttribute; -var - LAttr: TCustomAttribute; -begin - for LAttr in GetAttributes do - if LAttr is AAttrClass then - Exit(LAttr); - Result := nil; -end; - -function TRttiObjectHelper.GetAttribute: T; -begin - Result := T(GetAttribute(T)); -end; -{$ENDIF VER340} - -{ RequestChannelAttribute } - -constructor RequestChannelAttribute.Create(const AIdentifier: TRequestChannelIdentifier); -begin - inherited Create(); - FIdentifier := AIdentifier; -end; - -{ EventTypeAttribute } - -constructor EventTypeAttribute.Create(const AEventType: TEventChannelIdentifier); -begin - inherited Create(); - FEventType := AEventType; -end; - -{ ChannelTypeAttribute } - -constructor ChannelTypeAttribute.Create( - const AChannelType: TNotificationChannelIdentifier); -begin - inherited Create(); - FChannelType := AChannelType; -end; - -{ TNotificationChannel } - -class constructor TNotificationChannel.Create; -begin - FNextId := 0; -end; - -constructor TNotificationChannel.Create; -begin - inherited; - FChannelType := GetNotificationChannelType(); -end; - -procedure TNotificationChannel.GenerateId; -begin - TInterlocked.CompareExchange(FNextId, 0, High(Integer)); - FId := TInterlocked.Add(FNextId, 1); -end; - -class function TNotificationChannel.InitializeType: T; -var - LRttiCtx: TRttiContext; -begin - var LRttiType := LRttiCtx.GetType(TypeInfo(T)); - if LRttiType.IsInstance then - for var LRttiMethod in LRttiType.GetMethods() do - if LRttiMethod.HasExtendedInfo and LRttiMethod.IsConstructor and (Length(LRttiMethod.GetParameters()) = 0) then - Exit(LRttiMethod.Invoke(LRttiType.AsInstance.MetaclassType, []).AsType()); - - Result := Default(T); -end; - -class procedure TNotificationChannel.FinalizeType(var AValue); -var - LRttiCtx: TRttiContext; -begin - var LRttiType := LRttiCtx.GetType(TypeInfo(T)); - if LRttiType.IsInstance then - TObject(AValue).Free(); -end; - -class function TNotificationChannel.GetNotificationChannelType: TNotificationChannelIdentifier; -var - LRttiCtx: TRttiContext; -begin - var LRttiType := LRttiCtx.GetType(Self.ClassInfo); - while Assigned(LRttiType) do begin - var LAttribute := LRttiType.GetAttribute(); - if Assigned(LAttribute) then - Exit(LAttribute.ChannelType); - - LRttiType := LRttiType.BaseType; - end; - - raise EChannelTypeAttributeNotFound.Create('Channel type attribute not found.'); -end; - -{ TRequestChannel } - -constructor TRequestChannel.Create; -begin - inherited; - FRequestType := GetIdentifier(); - GenerateId(); -end; - -class function TRequestChannel.GetIdentifier: TRequestChannelIdentifier; -var - LRttiCtx: TRttiContext; -begin - var LRttiType := LRttiCtx.GetType(Self.ClassInfo); - while Assigned(LRttiType) do begin - var LAttribute := LRttiType.GetAttribute(); - if Assigned(LAttribute) then - Exit(LAttribute.Identifier); - - LRttiType := LRttiType.BaseType; - end; - - Assert(false, 'Request type attribute not found.'); -end; - -{ TRequestChannel } - -constructor TRequestChannel.Create; -begin - inherited; - FArguments := TNotificationChannel.InitializeType(); -end; - -destructor TRequestChannel.Destroy; -begin - TNotificationChannel.FinalizeType(FArguments); - inherited; -end; - -{ TResponseChannel } - -constructor TResponseChannel.Create; -begin - inherited; -end; - -destructor TResponseChannel.Destroy; -begin - inherited; -end; - -function TResponseChannel.GetId: TNotificationId; -begin - Result := FId; -end; - -procedure TResponseChannel.SetId(const Value: TNotificationId); -begin - FId := Value; -end; - -{ TResponseChannel } - -constructor TResponseChannel.Create; -begin - inherited; - FBody := TNotificationChannel.InitializeType(); -end; - -destructor TResponseChannel.Destroy; -begin - TNotificationChannel.FinalizeType(FBody); - inherited; -end; - -{ TEventChannel } - -constructor TEventChannel.Create; -begin - inherited; - FEventType := GeTEventChannelType(); - GenerateId(); -end; - -class function TEventChannel.GeTEventChannelType: TEventChannelIdentifier; -var - LRttiCtx: TRttiContext; -begin - var LRttiType := LRttiCtx.GetType(Self.ClassInfo); - while Assigned(LRttiType) do begin - var LAttribute := LRttiType.GetAttribute(); - if Assigned(LAttribute) then - Exit(LAttribute.EventType); - - LRttiType := LRttiType.BaseType; - end; - - Assert(false, 'Event type attribute not found.'); -end; - -{ TEventChannel } - -constructor TEventChannel.Create; -begin - inherited; - FBody := TNotificationChannel.InitializeType(); -end; - -destructor TEventChannel.Destroy; -begin - TNotificationChannel.FinalizeType(FBody); - inherited; -end; - -{ TErrorResponseChannel } - -constructor TErrorResponseChannel.Create(); -begin - inherited Create(); - FBody := TNotificationChannel.InitializeType(); -end; - -destructor TErrorResponseChannel.Destroy; -begin - TNotificationChannel.FinalizeType(FBody); - inherited; -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyTools.Protocol.Local' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Notification channels, channel identifiers *) +(* and contents *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyTools.Notification.Channel; + +interface + +uses + System.SysUtils, + System.SyncObjs; + +type + TRequestChannel = class; + TResponseChannel = class; + TErrorResponseChannel = class; + TEventChannel = class; + + TRequestNotificationAcceptCallback = reference to procedure(const Arg: T); + TRequestNotificationRejectCallback = reference to procedure(const Arg: TErrorResponseChannel); + + TEventNotificationCallback = reference to procedure(const Arg: T); + TEventNotificationCallback = TEventNotificationCallback; + + TRequestNotificationCallback = reference to function(const Arg: TRequest): TResponseChannel; + TRequestNotificationCallback = TRequestNotificationCallback; + + {$SCOPEDENUMS ON} + TNotificationChannelIdentifier = (Event, Request, Response); + {$SCOPEDENUMS OFF} + + TRequestChannelIdentifier = string; + TEventChannelIdentifier = string; + + TEmptyBody = class + end; + + TEmptyArgument = class + end; + + TErrorResponseBody = class + private + FCode: integer; + FMessage: string; + public + property Code: integer read FCode write FCode; + property Message: string read FMessage write FMessage; + end; + + ChannelTypeAttribute = class(TCustomAttribute) + private + FChannelType: TNotificationChannelIdentifier; + public + constructor Create(const AChannelType: TNotificationChannelIdentifier); + + property ChannelType: TNotificationChannelIdentifier read FChannelType; + end; + + RequestChannelAttribute = class(TCustomAttribute) + private + FIdentifier: TRequestChannelIdentifier; + public + constructor Create(const AIdentifier: TRequestChannelIdentifier); + + property Identifier: TRequestChannelIdentifier read FIdentifier; + end; + + EventTypeAttribute = class(TCustomAttribute) + private + FEventType: TEventChannelIdentifier; + public + constructor Create(const AEventType: TEventChannelIdentifier); + + property EventType: TEventChannelIdentifier read FEventType; + end; + + TNotificationId = integer; + + TNotificationChannel = class + private + class var FNextId: TNotificationId; + private + FChannelType: TNotificationChannelIdentifier; + FId: TNotificationId; + protected + procedure GenerateId(); + protected + class function InitializeType(): T; + class procedure FinalizeType(var AValue); + public + constructor Create(); + class constructor Create(); + + class function GetNotificationChannelType(): TNotificationChannelIdentifier; + + property ChannelType: TNotificationChannelIdentifier read FChannelType; + property Id: TNotificationId read FId; + end; + + [ChannelType(TNotificationChannelIdentifier.Request)] + TRequestChannel = class(TNotificationChannel) + private + FRequestType: TRequestChannelIdentifier; + public + class function GetIdentifier(): TRequestChannelIdentifier; + public + constructor Create(); + + property RequestType: TRequestChannelIdentifier read FRequestType; + end; + + TRequestChannel = class(TRequestChannel) + private + FArguments: TArguments; + public + constructor Create(); + destructor Destroy(); override; + + property Arguments: TArguments read FArguments write FArguments; + end; + + [ChannelType(TNotificationChannelIdentifier.Response)] + TResponseChannel = class(TNotificationChannel) + private + function GetId: TNotificationId; + procedure SetId(const Value: TNotificationId); + public + constructor Create(); + destructor Destroy(); override; + + property Id: TNotificationId read GetId write SetId; + end; + + TResponseChannel = class(TResponseChannel) + private + FBody: TBody; + public + constructor Create(); + destructor Destroy(); override; + + property Body: TBody read FBody; + end; + + TErrorResponseChannel = class(TResponseChannel); + + TErrorResponseChannel = class(TErrorResponseChannel) + private + FBody: TBody; + public + constructor Create(); overload; + destructor Destroy(); override; + + property Body: TBody read FBody; + end; + + [ChannelType(TNotificationChannelIdentifier.Event)] + TEventChannel = class(TNotificationChannel) + private + FEventType: TEventChannelIdentifier; + public + class function GetEventChannelType(): TEventChannelIdentifier; + public + constructor Create(); + + property EventType: TEventChannelIdentifier read FEventType; + end; + + TEventChannel = class(TEventChannel) + private + FBody: TBody; + public + constructor Create(); + destructor Destroy(); override; + + property Body: TBody read FBody; + end; + +implementation + +uses + System.Rtti, + PyTools.Exception; + +{$IFDEF VER340} +type + //For backward compatibility + TCustomAttributeClass = class of TCustomAttribute; + + TRttiObjectHelper = class helper for TRttiObject + public + function GetAttribute(AAttrClass: TCustomAttributeClass): TCustomAttribute; overload; + function GetAttribute: T; overload; inline; + end; +{$ENDIF VER340} + +{$IFDEF VER340} +{ TRttiObjectHelper } + +function TRttiObjectHelper.GetAttribute( + AAttrClass: TCustomAttributeClass): TCustomAttribute; +var + LAttr: TCustomAttribute; +begin + for LAttr in GetAttributes do + if LAttr is AAttrClass then + Exit(LAttr); + Result := nil; +end; + +function TRttiObjectHelper.GetAttribute: T; +begin + Result := T(GetAttribute(T)); +end; +{$ENDIF VER340} + +{ RequestChannelAttribute } + +constructor RequestChannelAttribute.Create(const AIdentifier: TRequestChannelIdentifier); +begin + inherited Create(); + FIdentifier := AIdentifier; +end; + +{ EventTypeAttribute } + +constructor EventTypeAttribute.Create(const AEventType: TEventChannelIdentifier); +begin + inherited Create(); + FEventType := AEventType; +end; + +{ ChannelTypeAttribute } + +constructor ChannelTypeAttribute.Create( + const AChannelType: TNotificationChannelIdentifier); +begin + inherited Create(); + FChannelType := AChannelType; +end; + +{ TNotificationChannel } + +class constructor TNotificationChannel.Create; +begin + FNextId := 0; +end; + +constructor TNotificationChannel.Create; +begin + inherited; + FChannelType := GetNotificationChannelType(); +end; + +procedure TNotificationChannel.GenerateId; +begin + TInterlocked.CompareExchange(FNextId, 0, High(Integer)); + FId := TInterlocked.Add(FNextId, 1); +end; + +class function TNotificationChannel.InitializeType: T; +var + LRttiCtx: TRttiContext; +begin + var LRttiType := LRttiCtx.GetType(TypeInfo(T)); + if LRttiType.IsInstance then + for var LRttiMethod in LRttiType.GetMethods() do + if LRttiMethod.HasExtendedInfo and LRttiMethod.IsConstructor and (Length(LRttiMethod.GetParameters()) = 0) then + Exit(LRttiMethod.Invoke(LRttiType.AsInstance.MetaclassType, []).AsType()); + + Result := Default(T); +end; + +class procedure TNotificationChannel.FinalizeType(var AValue); +var + LRttiCtx: TRttiContext; +begin + var LRttiType := LRttiCtx.GetType(TypeInfo(T)); + if LRttiType.IsInstance then + TObject(AValue).Free(); +end; + +class function TNotificationChannel.GetNotificationChannelType: TNotificationChannelIdentifier; +var + LRttiCtx: TRttiContext; +begin + var LRttiType := LRttiCtx.GetType(Self.ClassInfo); + while Assigned(LRttiType) do begin + var LAttribute := LRttiType.GetAttribute(); + if Assigned(LAttribute) then + Exit(LAttribute.ChannelType); + + LRttiType := LRttiType.BaseType; + end; + + raise EChannelTypeAttributeNotFound.Create('Channel type attribute not found.'); +end; + +{ TRequestChannel } + +constructor TRequestChannel.Create; +begin + inherited; + FRequestType := GetIdentifier(); + GenerateId(); +end; + +class function TRequestChannel.GetIdentifier: TRequestChannelIdentifier; +var + LRttiCtx: TRttiContext; +begin + var LRttiType := LRttiCtx.GetType(Self.ClassInfo); + while Assigned(LRttiType) do begin + var LAttribute := LRttiType.GetAttribute(); + if Assigned(LAttribute) then + Exit(LAttribute.Identifier); + + LRttiType := LRttiType.BaseType; + end; + + Assert(false, 'Request type attribute not found.'); +end; + +{ TRequestChannel } + +constructor TRequestChannel.Create; +begin + inherited; + FArguments := TNotificationChannel.InitializeType(); +end; + +destructor TRequestChannel.Destroy; +begin + TNotificationChannel.FinalizeType(FArguments); + inherited; +end; + +{ TResponseChannel } + +constructor TResponseChannel.Create; +begin + inherited; +end; + +destructor TResponseChannel.Destroy; +begin + inherited; +end; + +function TResponseChannel.GetId: TNotificationId; +begin + Result := FId; +end; + +procedure TResponseChannel.SetId(const Value: TNotificationId); +begin + FId := Value; +end; + +{ TResponseChannel } + +constructor TResponseChannel.Create; +begin + inherited; + FBody := TNotificationChannel.InitializeType(); +end; + +destructor TResponseChannel.Destroy; +begin + TNotificationChannel.FinalizeType(FBody); + inherited; +end; + +{ TEventChannel } + +constructor TEventChannel.Create; +begin + inherited; + FEventType := GeTEventChannelType(); + GenerateId(); +end; + +class function TEventChannel.GeTEventChannelType: TEventChannelIdentifier; +var + LRttiCtx: TRttiContext; +begin + var LRttiType := LRttiCtx.GetType(Self.ClassInfo); + while Assigned(LRttiType) do begin + var LAttribute := LRttiType.GetAttribute(); + if Assigned(LAttribute) then + Exit(LAttribute.EventType); + + LRttiType := LRttiType.BaseType; + end; + + Assert(false, 'Event type attribute not found.'); +end; + +{ TEventChannel } + +constructor TEventChannel.Create; +begin + inherited; + FBody := TNotificationChannel.InitializeType(); +end; + +destructor TEventChannel.Destroy; +begin + TNotificationChannel.FinalizeType(FBody); + inherited; +end; + +{ TErrorResponseChannel } + +constructor TErrorResponseChannel.Create(); +begin + inherited Create(); + FBody := TNotificationChannel.InitializeType(); +end; + +destructor TErrorResponseChannel.Destroy; +begin + TNotificationChannel.FinalizeType(FBody); + inherited; +end; + +end. diff --git a/src/Tools/Notification/PyTools.Notification.pas b/src/Tools/Notification/PyTools.Notification.pas index 39aa7c5..294bb07 100644 --- a/src/Tools/Notification/PyTools.Notification.pas +++ b/src/Tools/Notification/PyTools.Notification.pas @@ -1,74 +1,74 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyTools.Notification' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Notification types aliases *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyTools.Notification; - -interface - -uses - PyTools.Notification.Channel, - PyTools.Notification.Protocol, - PyTools.Notification.Protocol.Local; - -type - TNotificationChannelIdentifier = PyTools.Notification.Channel.TNotificationChannelIdentifier; - TRequestChannelIdentifier = PyTools.Notification.Channel.TRequestChannelIdentifier; - TEventChannelIdentifier = PyTools.Notification.Channel.TEventChannelIdentifier; - - TEventNotificationCallback = reference to procedure(const Arg: T); - TEventNotificationCallback = PyTools.Notification.Channel.TEventNotificationCallback; - - RequestTypeAttribute = PyTools.Notification.Channel.RequestChannelAttribute; - EventTypeAttribute = PyTools.Notification.Channel.EventTypeAttribute; - - TRequestChannel = PyTools.Notification.Channel.TRequestChannel; - TRequestChannel = class(PyTools.Notification.Channel.TRequestChannel); - TResponseChannel = PyTools.Notification.Channel.TResponseChannel; - TResponseChannel = class(PyTools.Notification.Channel.TResponseChannel); - TErrorResponseChannel = PyTools.Notification.Channel.TErrorResponseChannel; - TErrorResponseChannel = class(PyTools.Notification.Channel.TErrorResponseChannel); - TEventChannel = PyTools.Notification.Channel.TEventChannel; - TEventChannel = class(PyTools.Notification.Channel.TEventChannel); - - TEmptyBody = PyTools.Notification.Channel.TEmptyBody; - TEmptyArgument = PyTools.Notification.Channel.TEmptyArgument; - TErrorResponseBody = PyTools.Notification.Channel.TErrorResponseBody; - - IDisconnectable = PyTools.Notification.Protocol.IDisconnectable; - IAwait = PyTools.Notification.Protocol.IAwait; - IClientNotificationProtocol = PyTools.Notification.Protocol.IClientNotificationProtocol; - IServerNotificationProtocol = PyTools.Notification.Protocol.IServerNotificationProtocol; - - TLocalNotificationProtocol = PyTools.Notification.Protocol.Local.TLocalNotificationProtocol; - TAppNotificationProtocol = PyTools.Notification.Protocol.Local.TAppNotificationProtocol; - -implementation - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyTools.Notification' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Notification types aliases *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyTools.Notification; + +interface + +uses + PyTools.Notification.Channel, + PyTools.Notification.Protocol, + PyTools.Notification.Protocol.Local; + +type + TNotificationChannelIdentifier = PyTools.Notification.Channel.TNotificationChannelIdentifier; + TRequestChannelIdentifier = PyTools.Notification.Channel.TRequestChannelIdentifier; + TEventChannelIdentifier = PyTools.Notification.Channel.TEventChannelIdentifier; + + TEventNotificationCallback = reference to procedure(const Arg: T); + TEventNotificationCallback = PyTools.Notification.Channel.TEventNotificationCallback; + + RequestTypeAttribute = PyTools.Notification.Channel.RequestChannelAttribute; + EventTypeAttribute = PyTools.Notification.Channel.EventTypeAttribute; + + TRequestChannel = PyTools.Notification.Channel.TRequestChannel; + TRequestChannel = class(PyTools.Notification.Channel.TRequestChannel); + TResponseChannel = PyTools.Notification.Channel.TResponseChannel; + TResponseChannel = class(PyTools.Notification.Channel.TResponseChannel); + TErrorResponseChannel = PyTools.Notification.Channel.TErrorResponseChannel; + TErrorResponseChannel = class(PyTools.Notification.Channel.TErrorResponseChannel); + TEventChannel = PyTools.Notification.Channel.TEventChannel; + TEventChannel = class(PyTools.Notification.Channel.TEventChannel); + + TEmptyBody = PyTools.Notification.Channel.TEmptyBody; + TEmptyArgument = PyTools.Notification.Channel.TEmptyArgument; + TErrorResponseBody = PyTools.Notification.Channel.TErrorResponseBody; + + IDisconnectable = PyTools.Notification.Protocol.IDisconnectable; + IAwait = PyTools.Notification.Protocol.IAwait; + IClientNotificationProtocol = PyTools.Notification.Protocol.IClientNotificationProtocol; + IServerNotificationProtocol = PyTools.Notification.Protocol.IServerNotificationProtocol; + + TLocalNotificationProtocol = PyTools.Notification.Protocol.Local.TLocalNotificationProtocol; + TAppNotificationProtocol = PyTools.Notification.Protocol.Local.TAppNotificationProtocol; + +implementation + +end. diff --git a/src/Tools/PyTools.Cancelation.pas b/src/Tools/PyTools.Cancelation.pas index 25cdf72..4a96038 100644 --- a/src/Tools/PyTools.Cancelation.pas +++ b/src/Tools/PyTools.Cancelation.pas @@ -1,79 +1,79 @@ -(**************************************************************************) -(* *) -(* Module: Unit 'PyTools.Cancelation' *) -(* *) -(* Copyright (c) 2021 *) -(* Lucas Moura Belo - lmbelo *) -(* lucas.belo@live.com *) -(* Brazil *) -(* *) -(* Project page: https://github.com/Embarcadero/PythonEnviroments *) -(**************************************************************************) -(* Functionality: Unsubscribe from a notification *) -(* *) -(* *) -(**************************************************************************) -(* This source code is distributed with no WARRANTY, for no reason or use.*) -(* Everyone is allowed to use and change this code free for his own tasks *) -(* and projects, as long as this header and its copyright text is intact. *) -(* For changed versions of this code, which are public distributed the *) -(* following additional conditions have to be fullfilled: *) -(* 1) The header has to contain a comment on the change and the author of *) -(* it. *) -(* 2) A copy of the changed source has to be sent to the above E-Mail *) -(* address or my then valid address, if this is possible to the *) -(* author. *) -(* The second condition has the target to maintain an up to date central *) -(* version of the component. If this condition is not acceptable for *) -(* confidential or legal reasons, everyone is free to derive a component *) -(* or to generate a diff file to my or other original sources. *) -(**************************************************************************) -unit PyTools.Cancelation; - -interface - -uses - System.SysUtils, - System.SysConst, - System.Generics.Collections; - -type - ICancelation = interface - ['{749AF528-3209-4F5F-9CAA-58048285C275}'] - procedure Cancel(); - procedure CheckCancelled(); - function GetCancelled(): boolean; - - property IsCancelled: boolean read GetCancelled; - end; - - TCancelation = class(TInterfacedObject, ICancelation) - private - FCancelled: boolean; - function GetCancelled(): boolean; - public - procedure Cancel(); - procedure CheckCancelled(); - end; - -implementation - -{ TCancelation } - -procedure TCancelation.Cancel; -begin - FCancelled := true; -end; - -function TCancelation.GetCancelled: boolean; -begin - Result := FCancelled; -end; - -procedure TCancelation.CheckCancelled; -begin - if FCancelled then - raise EOperationCancelled.CreateRes(@SOperationCancelled); -end; - -end. +(**************************************************************************) +(* *) +(* Module: Unit 'PyTools.Cancelation' *) +(* *) +(* Copyright (c) 2021 *) +(* Lucas Moura Belo - lmbelo *) +(* lucas.belo@live.com *) +(* Brazil *) +(* *) +(* Project page: https://github.com/Embarcadero/PythonEnviroments *) +(**************************************************************************) +(* Functionality: Unsubscribe from a notification *) +(* *) +(* *) +(**************************************************************************) +(* This source code is distributed with no WARRANTY, for no reason or use.*) +(* Everyone is allowed to use and change this code free for his own tasks *) +(* and projects, as long as this header and its copyright text is intact. *) +(* For changed versions of this code, which are public distributed the *) +(* following additional conditions have to be fullfilled: *) +(* 1) The header has to contain a comment on the change and the author of *) +(* it. *) +(* 2) A copy of the changed source has to be sent to the above E-Mail *) +(* address or my then valid address, if this is possible to the *) +(* author. *) +(* The second condition has the target to maintain an up to date central *) +(* version of the component. If this condition is not acceptable for *) +(* confidential or legal reasons, everyone is free to derive a component *) +(* or to generate a diff file to my or other original sources. *) +(**************************************************************************) +unit PyTools.Cancelation; + +interface + +uses + System.SysUtils, + System.SysConst, + System.Generics.Collections; + +type + ICancelation = interface + ['{749AF528-3209-4F5F-9CAA-58048285C275}'] + procedure Cancel(); + procedure CheckCancelled(); + function GetCancelled(): boolean; + + property IsCancelled: boolean read GetCancelled; + end; + + TCancelation = class(TInterfacedObject, ICancelation) + private + FCancelled: boolean; + function GetCancelled(): boolean; + public + procedure Cancel(); + procedure CheckCancelled(); + end; + +implementation + +{ TCancelation } + +procedure TCancelation.Cancel; +begin + FCancelled := true; +end; + +function TCancelation.GetCancelled: boolean; +begin + Result := FCancelled; +end; + +procedure TCancelation.CheckCancelled; +begin + if FCancelled then + raise EOperationCancelled.CreateRes(@SOperationCancelled); +end; + +end. diff --git a/src/Tools/PyTools.Exception.pas b/src/Tools/PyTools.Exception.pas index ae6ed14..604e915 100644 --- a/src/Tools/PyTools.Exception.pas +++ b/src/Tools/PyTools.Exception.pas @@ -1,44 +1,44 @@ -unit PyTools.Exception; - -interface - -uses - System.SysUtils; - -type - EChannelTypeAttributeNotFound = class(Exception); - - EExecCmd = class(Exception); - - EForkFailed = class(EExecCmd); - - EPipeFailed = class(EExecCmd); - - EInvalidArgument = class(EExecCmd); - - EOperationNotPermitted = class(EExecCmd); - - ENoSuchProcess = class(EExecCmd); - - EWaitFailed = class(EExecCmd); - - EExecCmdFailed = class(EExecCmd) - private - FCode: integer; - public - constructor Create(const AMessage: string; const ACode: integer); reintroduce; - - property Code: integer read FCode; - end; - -implementation - -{ EExecCmdFailed } - -constructor EExecCmdFailed.Create(const AMessage: string; const ACode: integer); -begin - inherited Create(AMessage); - FCode := ACode; -end; - -end. +unit PyTools.Exception; + +interface + +uses + System.SysUtils; + +type + EChannelTypeAttributeNotFound = class(Exception); + + EExecCmd = class(Exception); + + EForkFailed = class(EExecCmd); + + EPipeFailed = class(EExecCmd); + + EInvalidArgument = class(EExecCmd); + + EOperationNotPermitted = class(EExecCmd); + + ENoSuchProcess = class(EExecCmd); + + EWaitFailed = class(EExecCmd); + + EExecCmdFailed = class(EExecCmd) + private + FCode: integer; + public + constructor Create(const AMessage: string; const ACode: integer); reintroduce; + + property Code: integer read FCode; + end; + +implementation + +{ EExecCmdFailed } + +constructor EExecCmdFailed.Create(const AMessage: string; const ACode: integer); +begin + inherited Create(AMessage); + FCode := ACode; +end; + +end.