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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -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;
- ///