From 0a94f36ad0e6941e583e9b75ad42f0bacc199d45 Mon Sep 17 00:00:00 2001 From: lmbelo Date: Tue, 28 Jun 2022 11:27:44 -0700 Subject: [PATCH 01/10] Adding an option to enable Python in a Delphi project and automatically add necessary files to deploy --- ...PythonEnvironmentsComponentSuite.groupproj | 18 +- packages/dclP4DEnvironmentProject.dpk | 46 + packages/dclP4DEnvironmentProject.dproj | 905 ++++++++++++++++++ python/android/3.10.4/arm/python3.10 | Bin 0 -> 7496 bytes python/android/3.10.4/arm64/python3.10 | Bin 0 -> 9496 bytes python/android/3.7.13/arm/python3.7 | Bin 0 -> 7496 bytes python/android/3.7.13/arm64/python3.7 | Bin 0 -> 9496 bytes python/android/3.8.13/arm/python3.8 | Bin 0 -> 7496 bytes python/android/3.8.13/arm64/python3.8 | Bin 0 -> 9496 bytes python/android/3.9.12/arm/python3.9 | Bin 0 -> 7496 bytes python/android/3.9.12/arm64/python3.9 | Bin 0 -> 9496 bytes src/Project/PyEnvironment.Project.Helper.pas | 765 +++++++++++++++ .../PyEnvironment.Project.ManagerMenu.pas | 376 ++++++++ src/Project/PyEnvironment.Project.Menu.pas | 202 ++++ .../PyEnvironment.Project.Registration.pas | 18 + src/Project/PyEnvironment.Project.Types.pas | 90 ++ src/Project/PyEnvironment.Project.pas | 199 ++++ 17 files changed, 2616 insertions(+), 3 deletions(-) create mode 100644 packages/dclP4DEnvironmentProject.dpk create mode 100644 packages/dclP4DEnvironmentProject.dproj create mode 100644 python/android/3.10.4/arm/python3.10 create mode 100644 python/android/3.10.4/arm64/python3.10 create mode 100644 python/android/3.7.13/arm/python3.7 create mode 100644 python/android/3.7.13/arm64/python3.7 create mode 100644 python/android/3.8.13/arm/python3.8 create mode 100644 python/android/3.8.13/arm64/python3.8 create mode 100644 python/android/3.9.12/arm/python3.9 create mode 100644 python/android/3.9.12/arm64/python3.9 create mode 100644 src/Project/PyEnvironment.Project.Helper.pas create mode 100644 src/Project/PyEnvironment.Project.ManagerMenu.pas create mode 100644 src/Project/PyEnvironment.Project.Menu.pas create mode 100644 src/Project/PyEnvironment.Project.Registration.pas create mode 100644 src/Project/PyEnvironment.Project.Types.pas create mode 100644 src/Project/PyEnvironment.Project.pas diff --git a/packages/P4DPythonEnvironmentsComponentSuite.groupproj b/packages/P4DPythonEnvironmentsComponentSuite.groupproj index 1170405..e809023 100644 --- a/packages/P4DPythonEnvironmentsComponentSuite.groupproj +++ b/packages/P4DPythonEnvironmentsComponentSuite.groupproj @@ -12,6 +12,9 @@ + + + Default.Personality.12 @@ -47,14 +50,23 @@ + + + + + + + + + - + - + - + diff --git a/packages/dclP4DEnvironmentProject.dpk b/packages/dclP4DEnvironmentProject.dpk new file mode 100644 index 0000000..3b444f3 --- /dev/null +++ b/packages/dclP4DEnvironmentProject.dpk @@ -0,0 +1,46 @@ +package dclP4DEnvironmentProject; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS ON} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION OFF} +{$OVERFLOWCHECKS ON} +{$RANGECHECKS ON} +{$REFERENCEINFO ON} +{$SAFEDIVIDE OFF} +{$STACKFRAMES ON} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE DEBUG} +{$ENDIF IMPLICITBUILDING} +{$DESCRIPTION 'P4D Environments IDE Extension'} +{$LIBSUFFIX AUTO} +{$DESIGNONLY} +{$IMPLICITBUILD ON} + +requires + rtl, + designide; + +contains + PyEnvironment.Project.Helper in '..\src\Project\PyEnvironment.Project.Helper.pas', + PyEnvironment.Project.ManagerMenu in '..\src\Project\PyEnvironment.Project.ManagerMenu.pas', + PyEnvironment.Project.Menu in '..\src\Project\PyEnvironment.Project.Menu.pas', + PyEnvironment.Project in '..\src\Project\PyEnvironment.Project.pas', + PyEnvironment.Project.Types in '..\src\Project\PyEnvironment.Project.Types.pas', + PyEnvironment.Project.Registration in '..\src\Project\PyEnvironment.Project.Registration.pas'; + +end. + diff --git a/packages/dclP4DEnvironmentProject.dproj b/packages/dclP4DEnvironmentProject.dproj new file mode 100644 index 0000000..c232921 --- /dev/null +++ b/packages/dclP4DEnvironmentProject.dproj @@ -0,0 +1,905 @@ + + + {4FE390C6-126B-4D5E-B1DF-8624DAB3A6CE} + dclP4DEnvironmentProject.dpk + 19.4 + None + True + Debug + Win32 + 1 + Package + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + ..\lib\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + true + true + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + All + 1033 + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + P4D Environments IDE Extension + true + $(Auto) + dclP4DEnvironmentProject + + + None + annotation-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.0.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.0.1.dex.jar;core-runtime-2.0.1.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.0.0.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.0.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.0.0.dex.jar;lifecycle-runtime-2.0.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.0.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar + + + None + annotation-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.0.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.0.1.dex.jar;core-runtime-2.0.1.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.0.0.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.0.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.0.0.dex.jar;lifecycle-runtime-2.0.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.0.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar + + + None + + + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + true + C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe + C:\Users\lucas\OneDrive\Documents\Embarcadero\Studio\Projects\deployabletest\Project1.dpr + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + + + + + + + + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + + + + Delphi.Personality.12 + Package + + + + P4D Environments IDE Extension + Embarcadero C++Builder Office 2000 Servers Package + Embarcadero C++Builder Office XP Servers Package + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + dclP4DEnvironmentProject.dpk + + + + + + true + + + + + true + + + + + true + + + + + dclP4DEnvironmentProject.bpl + true + + + + + 1 + + + 0 + + + + + classes + 64 + + + classes + 64 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + 1 + + + 1 + + + 0 + + + + + 1 + .framework + + + 1 + .framework + + + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + + + + 1 + + + 1 + + + 1 + + + + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + + + + + + False + False + False + False + False + False + True + False + + + 12 + + + + + diff --git a/python/android/3.10.4/arm/python3.10 b/python/android/3.10.4/arm/python3.10 new file mode 100644 index 0000000000000000000000000000000000000000..f5cdda63241011699d404028950b1fa2808f02cf GIT binary patch literal 7496 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 ZzJfC5J~b%l6x-M2*TBnrS{Va={{W5&2!{Xw literal 0 HcmV?d00001 diff --git a/python/android/3.10.4/arm64/python3.10 b/python/android/3.10.4/arm64/python3.10 new file mode 100644 index 0000000000000000000000000000000000000000..12a063abbc872d091d70166d478ce4d4e1a98c24 GIT binary patch literal 9496 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 literal 0 HcmV?d00001 diff --git a/python/android/3.8.13/arm/python3.8 b/python/android/3.8.13/arm/python3.8 new file mode 100644 index 0000000000000000000000000000000000000000..9a7c5e244c08e2cbee207d2603b87415b342d9ce GIT binary patch literal 7496 zcmeHMU2Ggz6}~gxO`UkXS=&vMrlFl81CEGiZLfdUMYL=jC$YLtUE{V?{7h$e#_Mf% zXP4Qro0Tl3s*vEJRir+lP{~H(A$cN5P(>9&enJ8v1V|OF;K6ZXwLlTA)S}WjnS;(q}9BfLP@dLJeuu^l?1>_3bCJLmUQWc+la z+3@UYx=?n~m9lfzc7y)k31W+!W4X1mB@RG#hXy}(E->8|jE)Rt2ZzUQ%lCGi1|v@0 z;2+*07k9|`+2RVG0oSGrt9KtLpIaa&MICZXLjy>OcUp^NG&X_E^}e*_zh687nR}1x z-7OO0TcEvo7K}%E88q5o7{CwFLy=S;)&>~m#LtHzx}K<-Wk<|3jpI$vZcI}!Y{x=lnVyLbg+?RvMAv%LTc|l1 zJ_&d0Nxz{c!$Kql#)<+$GFCnHHPh7Na{qdFSFD_3^zWUZ5E+2D; z3V-_h)8{WPzy9s79bt?F@D3njbN~sA!S`n`1pwpi^SPkmn0!|3&lv==0^LXdO~A@$ zLMV1JqADKrB_UqJ!#xQ8&mmn8>5Y)S3wjpb2DGrZ)ysE1R)_7dDjC z${UH)3!A-=$+v?)67n(dBDH!wrLAnL+R7W(Tdltx!k8&wJMhE9{6RC3hb`H?h5R#!0rnC zZ&v^*Sc@acnmV{FdC5ePv*oQv|2Uh%Dj6Km_suN z^JwP1%y`!zGk3lWeD7APR$&ec{t75_!0!P`d8Z`(VMr%HH6ZUl_k(i0yMeslFy*Dp z)RXt6BcTk|*Wbjv80&4&cQMW_&;$m>@AmaD{a3&-I4O?I6FR)9m|dl7lzEt(1<-| zjtq_##>cH}COa}dUL2i2s`YKnC7*wMIvBUHvacd>ltTxiYRZ%aBkjwOk zi#q^ASk?iM9axfFzLCcOOOt(~Ivm~&nu28y^&n0`}ZOPQZdy-vBH+HhBL)pFO zD~H;W4|IH~;|q6B^*n?P*cKORsG7oi5bG6f$pn0?GV@+Uc=Q8b_$FErdk0NC7B7&LsNk#I?vKECjqh{}<0{L^ zL?>k4YYB3g6X0Xwi)c}yx|!@GVP~MSy_hK0`0It$^drapX|^Smcw%3Vis%Y|8SllU zr_MYkO0|lm4eEnKkaHO{rfn9=LOBXHo|~Q4md3=MeRt&AmA&e|SeM!zyHh!!9BezJ z99BQ8eBK`gu3HdQ#8Bhl?_dohfP{Y@y&sY9g5k}G%(LOh);pRI8ss0rJ_o8|RgQtK zZ4IJ|FGGGdB2PhPtt&W>RPhbS%MtrLWdEQf1;b*57qK^eeb8|J5fN-~Qp)`xW$udr z0y{TEROX|P%G@JSncE~PZx4FF-v`@gRzf@nN3I{&RaFz>2kfW?&r?x*#3hI^tRGaq z{l}oPE~uzo;wof*D+r3(Eq)8i^Dlr7@p~}Q8rC}GXie%LkfSxNNV(Miz9hHSGWgyc zh8(S7-49vrY1Xi!JBZ(>4D2xt41hJVEaPdE0{NjGvM9PidM$mB zO(rZ}8GiA~vCbNW#d5_moW*JZl}IYWgIqW@&o;u7$0%Di4jEtNSGRnQ)h?fL%&NWp zq8VWU%~(M1itQTDxMsbM=!Ri6YN1wyMK4-LL~Rj#4V71QNsF<4jKwJUnP0wZdMLT5PB^W1V_5tT*6Y&s3km9c%cO)Ju@@=mV4Q$20lTC-^a+-XHq$EWh#ze!Uo&zm?Q&c#VfeT*3|b@mWfc@t%PKYh0IlUg-GuM+q_>zm@%uM06b|(O literal 0 HcmV?d00001 diff --git a/python/android/3.8.13/arm64/python3.8 b/python/android/3.8.13/arm64/python3.8 new file mode 100644 index 0000000000000000000000000000000000000000..b7dabd446a8706ad6ccf97f1cac5894f9333cdbe GIT binary patch literal 9496 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: 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; + +{ 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 = TPyEnvironmentProject.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; + + 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 := TPyEnvironmentProject.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(TPyEnvironmentProject.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 LDeployFileExistence in [TDeployFileExistence.NeedReplaced, TDeployFileExistence.DoesNotExist] 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 TPyEnvironmentProject.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], TPyEnvironmentProject.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], TPyEnvironmentProject.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(TPyEnvironmentProject.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(TPyEnvironmentProject.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 = TPyEnvironmentProject.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(TPyEnvironmentProject.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(TPyEnvironmentProject.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 TPyEnvironmentProject.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], TPyEnvironmentProject.PROJECT_USE_PYTHON) + else + LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper + .RemoveOptionValue( + LBaseConfiguration.Value[sDefine], TPyEnvironmentProject.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/PyEnvironment.Project.ManagerMenu.pas b/src/Project/PyEnvironment.Project.ManagerMenu.pas new file mode 100644 index 0000000..e3433ae --- /dev/null +++ b/src/Project/PyEnvironment.Project.ManagerMenu.pas @@ -0,0 +1,376 @@ +unit PyEnvironment.Project.ManagerMenu; + +interface + +uses + System.Classes, System.SysUtils, + ToolsAPI, + PyEnvironment.Project, + PyEnvironment.Project.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); + 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.Helper; + +{ 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 TPyEnvironmentProject.Found; +end; + +{ TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion } + +procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.AddSubItems( + const AProject: IOTAProject; const AList: IInterfaceList); +var + LPythonVersion: string; + LPosition: Integer; +begin + LPosition := 0; + for LPythonVersion in TPyEnvironmentProject.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); + 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 TPyEnvironmentProject.SUPPORTED_PLATFORMS) then begin + for LDeployFile in TPyEnvironmentProject.GetDeployFiles(LPythonVersion, APlatform) do + TPyEnvironmentProjectHelper.AddDeployFile(AProject, AConfig, LDeployFile); + end else begin + for LDeployFile in TPyEnvironmentProject.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.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/PyEnvironment.Project.Menu.pas b/src/Project/PyEnvironment.Project.Menu.pas new file mode 100644 index 0000000..2fc1704 --- /dev/null +++ b/src/Project/PyEnvironment.Project.Menu.pas @@ -0,0 +1,202 @@ +unit PyEnvironment.Project.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, + PyEnvironment.Project.Helper, + PyEnvironment.Project.Types, + PyEnvironment.Project.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 TPyEnvironmentProject.Found then + begin + LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; + LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); + if TPyEnvironmentProjectHelper.IsPyEnvironmentDefinedForPlatform(AProject, LPlatform, LConfig) then begin + if LPlatform in TPyEnvironmentProject.SUPPORTED_PLATFORMS then begin + TPyEnvironmentProjectHelper.RemoveUnexpectedDeployFilesOfClass(AProject, LConfig, LPlatform, TPyEnvironmentProject.GetDeployFiles(LPythonVersion, LPlatform)); + for LDeployFile in TPyEnvironmentProject.GetDeployFiles(LPythonVersion, LPlatform) do begin + if LDeployFile.CopyToOutput then begin + Assert(LDeployFile.LocalFileName <> ''); + TPyEnvironmentOTAHelper.TryCopyFileToOutputPathOfActiveBuild(AProject, TPath.Combine(TPyEnvironmentProject.AbsolutePath, LDeployFile.LocalFileName)); + end; + TPyEnvironmentProjectHelper.AddDeployFile(AProject, LConfig, LDeployFile); + end; + end else begin + for LDeployFile in TPyEnvironmentProject.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], + TPyEnvironmentProject.PROJECT_NO_USE_PYTHON])); + end; + end else begin + for LDeployFile in TPyEnvironmentProject.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 TPyEnvironmentProject.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. diff --git a/src/Project/PyEnvironment.Project.Registration.pas b/src/Project/PyEnvironment.Project.Registration.pas new file mode 100644 index 0000000..60d4c3e --- /dev/null +++ b/src/Project/PyEnvironment.Project.Registration.pas @@ -0,0 +1,18 @@ +unit PyEnvironment.Project.Registration; + +interface + +procedure Register(); + +implementation + +uses + PyEnvironment.Project.Menu; + +procedure Register(); +begin + TPyEnvironmentProjectMenuCreatorNotifier.Register(); + TPyEnvironmenCompileNotifier.Register(); +end; + +end. diff --git a/src/Project/PyEnvironment.Project.Types.pas b/src/Project/PyEnvironment.Project.Types.pas new file mode 100644 index 0000000..3acd931 --- /dev/null +++ b/src/Project/PyEnvironment.Project.Types.pas @@ -0,0 +1,90 @@ +unit PyEnvironment.Project.Types; + +interface + +uses + DeploymentAPI; + +type + TPyEnvironmentProjectConfig = (Release, Debug); + 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 + &Platform: TPyEnvironmentProjectPlatform; + LocalFileName: string; + RemotePath: string; + CopyToOutput: Boolean; + Required: Boolean; + Operation: TDeployOperation; + Condition: string; + + constructor Create(const APlatform: TPyEnvironmentProjectPlatform; + const ALocalFileName, ARemotePath: string; + const ACopyToOutput, ARequired: boolean; + const AOperation: TDeployOperation; const ACondition: string); + 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 + &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. diff --git a/src/Project/PyEnvironment.Project.pas b/src/Project/PyEnvironment.Project.pas new file mode 100644 index 0000000..24e6891 --- /dev/null +++ b/src/Project/PyEnvironment.Project.pas @@ -0,0 +1,199 @@ +unit PyEnvironment.Project; + +interface + +uses + System.Generics.Collections, + DeploymentAPI, + PyEnvironment.Project.Types; + +type + TPyEnvironmentProject = 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 = 'PythonEnvironment'; + PROJECT_USE_PYTHON = 'PYTHON'; + PROJECT_NO_USE_PYTHON = 'NOPYTHON'; + PYTHON_ENVIRONMENT_DIR_VARIABLE = 'PYTHONENVIRONMENTDIR'; + PYTHON_VERSIONS: array[0..3] of string = ('3.7', '3.8', '3.9', '3.10'); + {$IF CompilerVersion < 28} // Below RAD Studio XE7 + SUPPORTED_PLATFORMS = []; + {$ELSEIF CompilerVersion < 33} // RAD Studio XE7 to RAD Studio 10.2 Tokyo + SUPPORTED_PLATFORMS = [ + TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64]; + {$ELSEIF CompilerVersion < 35} // RAD Studio 10.3 Rio and RAD Studio 10.4 Sydney + SUPPORTED_PLATFORMS = [ + TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64, + TPyEnvironmentProjectPlatform.Android, TPyEnvironmentProjectPlatform.Android64]; + {$ELSE} // RAD Studio 11 Alexandria and newer + SUPPORTED_PLATFORMS = [ + TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64, + TPyEnvironmentProjectPlatform.Android, TPyEnvironmentProjectPlatform.Android64, + //TPyEnvironmentProjectPlatform.iOSDevice64, + TPyEnvironmentProjectPlatform.OSX64, TPyEnvironmentProjectPlatform.OSXARM64, + TPyEnvironmentProjectPlatform.Linux64]; + {$ENDIF} + 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.Helper; + +{ TPyEnvironmentProject } + +class constructor TPyEnvironmentProject.Create; +begin + FDeployableFiles := TDictionary>.Create(); + + FDeployableFiles.Add('3.7', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.7.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.7.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.7.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.7.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.7.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.13\arm\libpython3.7m.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\libpython3.7m.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\libpython3.7m.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.13\arm\python3.7', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\python3.7', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\python3.7', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.7.13-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 + //TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.7.13-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.7.13-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 + ]); + + FDeployableFiles.Add('3.8', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.8.10-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.8.10-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.8.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.8.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.8.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.13\arm\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\libpython3.8.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.13\arm\python3.8', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\python3.8', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\python3.8', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.8.13-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.8.13-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.8.13-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 + ]); + + FDeployableFiles.Add('3.9', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.9.12-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.9.12-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.9.12-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.9.12-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.9.12-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.12\arm\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\libpython3.9.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.12\arm\python3.9', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\python3.9', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\python3.9', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.9.12-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.9.12-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.9.12-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 + ]); + + FDeployableFiles.Add('3.10', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.10.4-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.10.4-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.10.4-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.10.4-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.10.4-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.4\arm\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\libpython3.10.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.4\arm\python3.10', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\python3.10', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\python3.10', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.10.4-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.10.4-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.10.4-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 + ]); +end; + +class destructor TPyEnvironmentProject.Destroy; +begin + FDeployableFiles.Free(); +end; + +class procedure TPyEnvironmentProject.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 TPyEnvironmentProject.GetAbsolutePath: string; +begin + if not FPathChecked then + GetPath(); + Result := FAbsolutePath; +end; + +class function TPyEnvironmentProject.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 TPyEnvironmentProject.GetFound: Boolean; +begin + Result := not Path.IsEmpty; +end; + +class function TPyEnvironmentProject.GetPath: string; +begin + if not FPathChecked then begin + FindPath(FPath, FAbsolutePath); + FPathChecked := True; + end; + Result := FPath; +end; + +class function TPyEnvironmentProject.IsValidPythonEnvironmentDir( + const APath: string): Boolean; +begin + Result := TDirectory.Exists(APath); +end; + +end. From 29d1402f4835e13ddbba71562e14ca81b977fd08 Mon Sep 17 00:00:00 2001 From: lmbelo Date: Sun, 3 Jul 2022 08:48:06 -0700 Subject: [PATCH 02/10] Including an option to the Project menu to enable Pytho and select the desired version --- .../IDE/PyEnvironment.Project.IDE.Deploy.pas | 188 +++++ .../IDE/PyEnvironment.Project.IDE.Helper.pas | 772 ++++++++++++++++++ .../PyEnvironment.Project.IDE.ManagerMenu.pas | 377 +++++++++ .../IDE/PyEnvironment.Project.IDE.Menu.pas | 206 +++++ ...PyEnvironment.Project.IDE.Registration.pas | 18 + .../IDE/PyEnvironment.Project.IDE.Types.pas | 109 +++ src/PyEnvionment.Editors.pas | 58 ++ 7 files changed, 1728 insertions(+) create mode 100644 src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas create mode 100644 src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas create mode 100644 src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas create mode 100644 src/Project/IDE/PyEnvironment.Project.IDE.Menu.pas create mode 100644 src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas create mode 100644 src/Project/IDE/PyEnvironment.Project.IDE.Types.pas create mode 100644 src/PyEnvionment.Editors.pas diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas new file mode 100644 index 0000000..f606d0c --- /dev/null +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Deploy.pas @@ -0,0 +1,188 @@ +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..3] of string = ('3.7', '3.8', '3.9', '3.10'); + 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(); + + FDeployableFiles.Add('3.7', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.7.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.7.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.7.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.7.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.7.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.13\arm\libpython3.7m.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\libpython3.7m.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm\libpython3.7m.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.13\arm\python3.7', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\python3.7', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm\python3.7', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.7.13-x86_64.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSX64 + //TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.7.13-universal2.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.7.13-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') // Linux64 + ]); + + FDeployableFiles.Add('3.8', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.8.10-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.8.10-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.8.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.8.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.8.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.13\arm\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\libpython3.8.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.13\arm\python3.8', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\python3.8', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm\python3.8', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.8.13-x86_64.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSX64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.8.13-universal2.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.8.13-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') // Linux64 + ]); + + FDeployableFiles.Add('3.9', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.9.12-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.9.12-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.9.12-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.9.12-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.9.12-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.12\arm\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\libpython3.9.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.12\arm\python3.9', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\python3.9', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm\python3.9', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.9.12-x86_64.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSX64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.9.12-universal2.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.9.12-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') // Linux64 + ]); + + FDeployableFiles.Add('3.10', [ + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'python\python3-windows-3.10.4-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'python\python3-windows-3.10.4-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\python3-android-3.10.4-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.10.4-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\python3-android-3.10.4-arm.zip', '.\assets\internal', False, True, TDeployOperation.doCopyOnly, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.4\arm\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\libpython3.10.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.4\arm\python3.10', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\python3.10', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm\python3.10', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'python\python3-macos-3.10.4-x86_64.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSX64 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'python\python3-macos-3.10.4-universal2.zip', 'Contents\MacOS\', True, True, TDeployOperation.doCopyOnly, ''), // OSXARM64 //3.7 is not available for M1 + TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'python\python3-linux-3.10.4-x86_64.zip', '.\', True, True, TDeployOperation.doCopyOnly, '') // Linux64 + ]); +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. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas new file mode 100644 index 0000000..bd47583 --- /dev/null +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Helper.pas @@ -0,0 +1,772 @@ +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. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas b/src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas new file mode 100644 index 0000000..321f7f0 --- /dev/null +++ b/src/Project/IDE/PyEnvironment.Project.IDE.ManagerMenu.pas @@ -0,0 +1,377 @@ +unit PyEnvironment.Project.IDE.ManagerMenu; + +interface + +uses + System.Classes, System.SysUtils, + ToolsAPI, + 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); + 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, + PyEnvironment.Project.IDE.Helper; + +{ 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); + 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.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 new file mode 100644 index 0000000..5a0190d --- /dev/null +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Menu.pas @@ -0,0 +1,206 @@ +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. diff --git a/src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas b/src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas new file mode 100644 index 0000000..dc5ffa7 --- /dev/null +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Registration.pas @@ -0,0 +1,18 @@ +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 new file mode 100644 index 0000000..f528ced --- /dev/null +++ b/src/Project/IDE/PyEnvironment.Project.IDE.Types.pas @@ -0,0 +1,109 @@ +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. diff --git a/src/PyEnvionment.Editors.pas b/src/PyEnvionment.Editors.pas new file mode 100644 index 0000000..227db7c --- /dev/null +++ b/src/PyEnvionment.Editors.pas @@ -0,0 +1,58 @@ +unit PyEnvionment.Editors; + +interface + +uses + System.SysUtils, Classes, DesignIntf, DesignEditors; + +type + TPyEnvironmentEmbeddedPythonVersionProperty = class (TStringProperty) + public + function GetValue: string; override; + procedure SetValue(const Value: string); override; + end; + +procedure Register(); + +implementation + +uses + ToolsAPI, + PyEnvironment.Project.IDE.Helper, + PyEnvironment.Embeddable; + +procedure Register(); +begin + RegisterPropertyEditor(TypeInfo(string), TPyEmbeddedEnvironment, 'PythonVersion', TPyEnvironmentEmbeddedPythonVersionProperty); +end; + +{ TPyEnvironmentEmbeddedPythonVersionProperty } + +function TPyEnvironmentEmbeddedPythonVersionProperty.GetValue: string; +var + LProject: IOTAProject; +begin + LProject := GetActiveProject(); + if Assigned(LProject) then begin + if TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[LProject] then + Result := TPyEnvironmentProjectHelper.CurrentPythonVersion[LProject] + else + Result := inherited; + end else + Result := inherited; +end; + +procedure TPyEnvironmentEmbeddedPythonVersionProperty.SetValue( + const Value: string); +var + LProject: IOTAProject; +begin + LProject := GetActiveProject(); + if Assigned(LProject) then begin + if not TPyEnvironmentProjectHelper.IsPyEnvironmentDefined[LProject] then + inherited; + end else + inherited; +end; + +end. From 12e8b74ff00542f66b3de11458ef858649202f9b Mon Sep 17 00:00:00 2001 From: lmbelo Date: Sun, 3 Jul 2022 08:48:55 -0700 Subject: [PATCH 03/10] Extending the path settings to support the DEPLOY_PATH variable --- src/PyEnvironment.Path.pas | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/PyEnvironment.Path.pas b/src/PyEnvironment.Path.pas index ba6e427..d34f3ef 100644 --- a/src/PyEnvironment.Path.pas +++ b/src/PyEnvironment.Path.pas @@ -34,6 +34,9 @@ interface type TPyEnvironmentPath = class + public const + DEPLOY_PATH = '$(DEPLOY_PATH)'; + public /// /// This function might resolve path variables, relative paths and whatever regarding paths /// @@ -48,16 +51,16 @@ implementation { TPyEnvironmentPath } class function TPyEnvironmentPath.ResolvePath(const APath: string): string; -var - LFilePath: string; begin - if (APath <> ExpandFileName(APath)) then begin + if (APath <> ExpandFileName(APath)) or (APath = DEPLOY_PATH) then begin {$IFDEF ANDROID} - LFilePath := TPath.GetDocumentsPath(); + Result := TPath.GetDocumentsPath(); {$ELSE} - LFilePath := TPath.GetDirectoryName(GetModuleName(HInstance)); + Result := TPath.GetDirectoryName(GetModuleName(HInstance)); {$ENDIF} - Result := TPath.Combine(LFilePath, APath); + + if (APath <> DEPLOY_PATH) then + Result := TPath.Combine(Result, APath); end else Result := APath; end; From 1a642c3e87e37fbde999395387efef6336c09a03 Mon Sep 17 00:00:00 2001 From: lmbelo Date: Sun, 3 Jul 2022 08:49:28 -0700 Subject: [PATCH 04/10] Patching the tmp dir for Android --- src/Tools/PyTools.ExecCmd.Args.pas | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Tools/PyTools.ExecCmd.Args.pas b/src/Tools/PyTools.ExecCmd.Args.pas index a32caad..0bada40 100644 --- a/src/Tools/PyTools.ExecCmd.Args.pas +++ b/src/Tools/PyTools.ExecCmd.Args.pas @@ -74,6 +74,9 @@ class function TExecCmdArgs.BuildEnvp(const AHome, AExecutable, Result := ['LD_LIBRARY_PATH=' + ExtractFileDir(ASharedLibrary), 'PYTHONHOME=' + AHome, 'PATH=' + ExtractFileDir(AExecutable)]; + {$IFDEF ANDROID} + Result := Result + ['TMPDIR=' + TPath.GetTempPath()]; + {$ENDIF ANDROID} {$ENDIF MSWINDOWS} end; From 36b56b7f78062d9bca1e56080b5a8fdc8e9c9e43 Mon Sep 17 00:00:00 2001 From: lmbelo Date: Sun, 3 Jul 2022 08:50:47 -0700 Subject: [PATCH 05/10] Including setup and activate overloads to skip Python version from params and use the current set up --- src/PyEnvironment.pas | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/PyEnvironment.pas b/src/PyEnvironment.pas index 5526c59..016ebef 100644 --- a/src/PyEnvironment.pas +++ b/src/PyEnvironment.pas @@ -96,10 +96,10 @@ TPyCustomEnvironment = class(TComponent, IEnvironmentNotifier Date: Sun, 3 Jul 2022 08:53:04 -0700 Subject: [PATCH 06/10] Renaming the IDE coding part of the new Python project definition and including the run-time Python project track --- src/Project/PyEnvironment.Project.Helper.pas | 765 ------------------ .../PyEnvironment.Project.ManagerMenu.pas | 376 --------- src/Project/PyEnvironment.Project.Menu.pas | 202 ----- .../PyEnvironment.Project.Registration.pas | 18 - src/Project/PyEnvironment.Project.Types.pas | 90 --- src/Project/PyEnvironment.Project.pas | 202 +---- 6 files changed, 34 insertions(+), 1619 deletions(-) delete mode 100644 src/Project/PyEnvironment.Project.Helper.pas delete mode 100644 src/Project/PyEnvironment.Project.ManagerMenu.pas delete mode 100644 src/Project/PyEnvironment.Project.Menu.pas delete mode 100644 src/Project/PyEnvironment.Project.Registration.pas delete mode 100644 src/Project/PyEnvironment.Project.Types.pas diff --git a/src/Project/PyEnvironment.Project.Helper.pas b/src/Project/PyEnvironment.Project.Helper.pas deleted file mode 100644 index 45c95b0..0000000 --- a/src/Project/PyEnvironment.Project.Helper.pas +++ /dev/null @@ -1,765 +0,0 @@ -unit PyEnvironment.Project.Helper; - -interface - -uses - System.Classes, - ToolsAPI, DeploymentAPI, - PyEnvironment.Project.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; - -{ 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 = TPyEnvironmentProject.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; - - 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 := TPyEnvironmentProject.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(TPyEnvironmentProject.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 LDeployFileExistence in [TDeployFileExistence.NeedReplaced, TDeployFileExistence.DoesNotExist] 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 TPyEnvironmentProject.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], TPyEnvironmentProject.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], TPyEnvironmentProject.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(TPyEnvironmentProject.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(TPyEnvironmentProject.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 = TPyEnvironmentProject.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(TPyEnvironmentProject.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(TPyEnvironmentProject.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 TPyEnvironmentProject.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], TPyEnvironmentProject.PROJECT_USE_PYTHON) - else - LBaseConfiguration.Value[sDefine] := TPyEnvironmentOTAHelper - .RemoveOptionValue( - LBaseConfiguration.Value[sDefine], TPyEnvironmentProject.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/PyEnvironment.Project.ManagerMenu.pas b/src/Project/PyEnvironment.Project.ManagerMenu.pas deleted file mode 100644 index e3433ae..0000000 --- a/src/Project/PyEnvironment.Project.ManagerMenu.pas +++ /dev/null @@ -1,376 +0,0 @@ -unit PyEnvironment.Project.ManagerMenu; - -interface - -uses - System.Classes, System.SysUtils, - ToolsAPI, - PyEnvironment.Project, - PyEnvironment.Project.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); - 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.Helper; - -{ 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 TPyEnvironmentProject.Found; -end; - -{ TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion } - -procedure TPyEnvironmentProjectManagerMenuPythonEnvironmentVersion.AddSubItems( - const AProject: IOTAProject; const AList: IInterfaceList); -var - LPythonVersion: string; - LPosition: Integer; -begin - LPosition := 0; - for LPythonVersion in TPyEnvironmentProject.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); - 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 TPyEnvironmentProject.SUPPORTED_PLATFORMS) then begin - for LDeployFile in TPyEnvironmentProject.GetDeployFiles(LPythonVersion, APlatform) do - TPyEnvironmentProjectHelper.AddDeployFile(AProject, AConfig, LDeployFile); - end else begin - for LDeployFile in TPyEnvironmentProject.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.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/PyEnvironment.Project.Menu.pas b/src/Project/PyEnvironment.Project.Menu.pas deleted file mode 100644 index 2fc1704..0000000 --- a/src/Project/PyEnvironment.Project.Menu.pas +++ /dev/null @@ -1,202 +0,0 @@ -unit PyEnvironment.Project.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, - PyEnvironment.Project.Helper, - PyEnvironment.Project.Types, - PyEnvironment.Project.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 TPyEnvironmentProject.Found then - begin - LPythonVersion := TPyEnvironmentProjectHelper.CurrentPythonVersion[AProject]; - LConfig := TPyEnvironmentProjectConfig.FromString(AProject.CurrentConfiguration); - if TPyEnvironmentProjectHelper.IsPyEnvironmentDefinedForPlatform(AProject, LPlatform, LConfig) then begin - if LPlatform in TPyEnvironmentProject.SUPPORTED_PLATFORMS then begin - TPyEnvironmentProjectHelper.RemoveUnexpectedDeployFilesOfClass(AProject, LConfig, LPlatform, TPyEnvironmentProject.GetDeployFiles(LPythonVersion, LPlatform)); - for LDeployFile in TPyEnvironmentProject.GetDeployFiles(LPythonVersion, LPlatform) do begin - if LDeployFile.CopyToOutput then begin - Assert(LDeployFile.LocalFileName <> ''); - TPyEnvironmentOTAHelper.TryCopyFileToOutputPathOfActiveBuild(AProject, TPath.Combine(TPyEnvironmentProject.AbsolutePath, LDeployFile.LocalFileName)); - end; - TPyEnvironmentProjectHelper.AddDeployFile(AProject, LConfig, LDeployFile); - end; - end else begin - for LDeployFile in TPyEnvironmentProject.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], - TPyEnvironmentProject.PROJECT_NO_USE_PYTHON])); - end; - end else begin - for LDeployFile in TPyEnvironmentProject.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 TPyEnvironmentProject.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. diff --git a/src/Project/PyEnvironment.Project.Registration.pas b/src/Project/PyEnvironment.Project.Registration.pas deleted file mode 100644 index 60d4c3e..0000000 --- a/src/Project/PyEnvironment.Project.Registration.pas +++ /dev/null @@ -1,18 +0,0 @@ -unit PyEnvironment.Project.Registration; - -interface - -procedure Register(); - -implementation - -uses - PyEnvironment.Project.Menu; - -procedure Register(); -begin - TPyEnvironmentProjectMenuCreatorNotifier.Register(); - TPyEnvironmenCompileNotifier.Register(); -end; - -end. diff --git a/src/Project/PyEnvironment.Project.Types.pas b/src/Project/PyEnvironment.Project.Types.pas deleted file mode 100644 index 3acd931..0000000 --- a/src/Project/PyEnvironment.Project.Types.pas +++ /dev/null @@ -1,90 +0,0 @@ -unit PyEnvironment.Project.Types; - -interface - -uses - DeploymentAPI; - -type - TPyEnvironmentProjectConfig = (Release, Debug); - 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 - &Platform: TPyEnvironmentProjectPlatform; - LocalFileName: string; - RemotePath: string; - CopyToOutput: Boolean; - Required: Boolean; - Operation: TDeployOperation; - Condition: string; - - constructor Create(const APlatform: TPyEnvironmentProjectPlatform; - const ALocalFileName, ARemotePath: string; - const ACopyToOutput, ARequired: boolean; - const AOperation: TDeployOperation; const ACondition: string); - 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 - &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. diff --git a/src/Project/PyEnvironment.Project.pas b/src/Project/PyEnvironment.Project.pas index 24e6891..f38c93c 100644 --- a/src/Project/PyEnvironment.Project.pas +++ b/src/Project/PyEnvironment.Project.pas @@ -3,197 +3,63 @@ interface uses - System.Generics.Collections, - DeploymentAPI, - PyEnvironment.Project.Types; + System.Classes; type TPyEnvironmentProject = 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 = 'PythonEnvironment'; - PROJECT_USE_PYTHON = 'PYTHON'; - PROJECT_NO_USE_PYTHON = 'NOPYTHON'; - PYTHON_ENVIRONMENT_DIR_VARIABLE = 'PYTHONENVIRONMENTDIR'; - PYTHON_VERSIONS: array[0..3] of string = ('3.7', '3.8', '3.9', '3.10'); - {$IF CompilerVersion < 28} // Below RAD Studio XE7 - SUPPORTED_PLATFORMS = []; - {$ELSEIF CompilerVersion < 33} // RAD Studio XE7 to RAD Studio 10.2 Tokyo - SUPPORTED_PLATFORMS = [ - TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64]; - {$ELSEIF CompilerVersion < 35} // RAD Studio 10.3 Rio and RAD Studio 10.4 Sydney - SUPPORTED_PLATFORMS = [ - TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64, - TPyEnvironmentProjectPlatform.Android, TPyEnvironmentProjectPlatform.Android64]; - {$ELSE} // RAD Studio 11 Alexandria and newer - SUPPORTED_PLATFORMS = [ - TPyEnvironmentProjectPlatform.Win32, TPyEnvironmentProjectPlatform.Win64, - TPyEnvironmentProjectPlatform.Android, TPyEnvironmentProjectPlatform.Android64, - //TPyEnvironmentProjectPlatform.iOSDevice64, - TPyEnvironmentProjectPlatform.OSX64, TPyEnvironmentProjectPlatform.OSXARM64, - TPyEnvironmentProjectPlatform.Linux64]; - {$ENDIF} - public + private + function GetPythonVersion: string; + function GetEnabled: boolean; + private 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; + class destructor Destory(); + public + property Enabled: boolean read GetEnabled; + property PythonVersion: string read GetPythonVersion; end; +var + PythonProject: TPyEnvironmentProject; + implementation uses - System.SysUtils, System.IOUtils, - PyEnvironment.Project.Helper; + System.SysUtils, System.Rtti; { TPyEnvironmentProject } class constructor TPyEnvironmentProject.Create; begin - FDeployableFiles := TDictionary>.Create(); - - FDeployableFiles.Add('3.7', [ - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.7.9-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.7.9-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.7.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.7.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.7.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.13\arm\libpython3.7m.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\libpython3.7m.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\libpython3.7m.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.7.13\arm\python3.7', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\python3.7', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.7.13\arm64\python3.7', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.7.13-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 - //TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.7.13-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.7.13-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 - ]); - - FDeployableFiles.Add('3.8', [ - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.8.10-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.8.10-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.8.13-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.8.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.8.13-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.13\arm\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\libpython3.8.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\libpython3.8.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.8.13\arm\python3.8', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\python3.8', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.8.13\arm64\python3.8', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.8.13-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.8.13-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.8.13-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 - ]); - - FDeployableFiles.Add('3.9', [ - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.9.12-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.9.12-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.9.12-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.9.12-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.9.12-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.12\arm\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\libpython3.9.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\libpython3.9.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.9.12\arm\python3.9', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\python3.9', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.9.12\arm64\python3.9', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.9.12-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.9.12-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.9.12-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 - ]); - - FDeployableFiles.Add('3.10', [ - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win32, 'Python\python3-windows-3.10.4-win32.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win32 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Win64, 'Python\python3-windows-3.10.4-amd64.zip', '.\', True, True, TDeployOperation.doCopyOnly, ''), // Win64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'Python\python3-android-3.10.4-arm.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.10.4-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'Python\python3-android-3.10.4-arm64.zip', '.\assets\internal', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.4\arm\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\libpython3.10.so', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\libpython3.10.so', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android, 'python\android\3.10.4\arm\python3.10', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, ''), // Android - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\python3.10', 'library\lib\arm64-v8a\', False, True, TDeployOperation.doSetExecBit, ''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Android64, 'python\android\3.10.4\arm64\python3.10', 'library\lib\armeabi-v7a\', False, True, TDeployOperation.doSetExecBit, '''$(AndroidAppBundle)''==''true'''), // Android64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSX64, 'Python\python3-macos-3.10.4-x86_64.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSX64 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.OSXARM64, 'Python\python3-macos-3.10.4-universal2.zip', 'Contents\MacOS\', False, True, TDeployOperation.doSetExecBit, ''), // OSXARM64 //3.7 is not available for M1 - TPyEnvironmentDeployFile.Create(TPyEnvironmentProjectPlatform.Linux64, 'Python\python3-linux-3.10.4-x86_64.zip', '.\', False, True, TDeployOperation.doSetExecBit, '') // Linux64 - ]); -end; - -class destructor TPyEnvironmentProject.Destroy; -begin - FDeployableFiles.Free(); + PythonProject := TPyEnvironmentProject.Create(); end; -class procedure TPyEnvironmentProject.FindPath(out APath, - AAbsolutePath: string); +class destructor TPyEnvironmentProject.Destory; begin - AAbsolutePath := TPyEnvironmentOTAHelper.GetEnvironmentVar(PYTHON_ENVIRONMENT_DIR_VARIABLE, True); - if IsValidPythonEnvironmentDir(AAbsolutePath) then - APath := '$(' + PYTHON_ENVIRONMENT_DIR_VARIABLE + ')' - else begin - APath := ''; - AAbsolutePath := ''; - end; + FreeAndNil(PythonProject); end; -class function TPyEnvironmentProject.GetAbsolutePath: string; +function TPyEnvironmentProject.GetEnabled: boolean; begin - if not FPathChecked then - GetPath(); - Result := FAbsolutePath; -end; - -class function TPyEnvironmentProject.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 TPyEnvironmentProject.GetFound: Boolean; -begin - Result := not Path.IsEmpty; -end; - -class function TPyEnvironmentProject.GetPath: string; -begin - if not FPathChecked then begin - FindPath(FPath, FAbsolutePath); - FPathChecked := True; - end; - Result := FPath; + {$IFDEF PYTHON} + Result := true; + {$ELSE} + Result := false; + {$ENDIF} end; -class function TPyEnvironmentProject.IsValidPythonEnvironmentDir( - const APath: string): Boolean; +function TPyEnvironmentProject.GetPythonVersion: string; begin - Result := TDirectory.Exists(APath); + {$IFDEF PYTHONVER37} + Result := '3.7'; + {$ELSEIF DEFINED(PYTHONVER38)} + Result := '3.8'; + {$ELSEIF DEFINED(PYTHONVER39)} + Result := '3.9'; + {$ELSEIF DEFINED(PYTHONVER310)} + Result := '3.10'; + {$ELSE} + Result := String.Empty; + {$ENDIF} end; end. From 2a0686a317ad7d08c554a4454d863c8149a96c7f Mon Sep 17 00:00:00 2001 From: lmbelo Date: Sun, 3 Jul 2022 08:54:11 -0700 Subject: [PATCH 07/10] Extending the Embedded environment to support the new Python project deployables --- src/Embeddable/PyEnvironment.Embeddable.pas | 183 +++++++++++++++----- 1 file changed, 137 insertions(+), 46 deletions(-) diff --git a/src/Embeddable/PyEnvironment.Embeddable.pas b/src/Embeddable/PyEnvironment.Embeddable.pas index 1219fd0..7f0557c 100644 --- a/src/Embeddable/PyEnvironment.Embeddable.pas +++ b/src/Embeddable/PyEnvironment.Embeddable.pas @@ -89,12 +89,19 @@ TPyCustomEmbeddableDistribution = class(TPyDistribution) TPyEmbeddableDistribution = class(TPyCustomEmbeddableDistribution) private FScanned: boolean; + FDeleteEmbeddable: boolean; + procedure DoDeleteEmbeddable(); protected procedure LoadSettings(); override; public + procedure Setup(); 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); @@ -111,20 +118,28 @@ TPyCustomEmbeddedEnvironment = class(TPyEnvironment) [ComponentPlatforms(pidAllPlatforms)] TPyEmbeddedEnvironment = class(TPyCustomEmbeddedEnvironment) private type + TScanRule = (srFolder, srFileName); TScanner = class(TPersistent) private FAutoScan: boolean; + FScanRule: TScanRule; FEmbeddablesPath: string; FEnvironmentPath: string; + FDeleteEmbeddable: boolean; public procedure Scan(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; @@ -148,7 +163,8 @@ implementation System.IOUtils, System.Character, System.StrUtils, PyEnvironment.Path, PyTools.ExecCmd, - PyEnvironment.Notification + PyEnvironment.Notification, + PyEnvironment.Project {$IFDEF POSIX} , Posix.SysStat, Posix.Stdlib, Posix.String_, Posix.Errno {$ENDIF} @@ -199,41 +215,56 @@ function TPyCustomEmbeddableDistribution.FileIsExecutable( {$ENDIF POSIX} function TPyCustomEmbeddableDistribution.FindExecutable: string; -var - LFiles: TArray; + + function DoSearch(const APath: string): TArray; {$IFDEF POSIX} - LFile: string; + var + LFile: string; {$ENDIF POSIX} + begin + Result := TDirectory.GetFiles(APath, 'python*', TSearchOption.soTopDirectoryOnly, + function(const Path: string; const SearchRec: TSearchRec): boolean + begin + Result := Char.IsDigit(SearchRec.Name, Length(SearchRec.Name) - 1); + end); + + {$IFDEF POSIX} + for LFile in Result do begin + if (TPath.GetFileName(LFile) = 'python' + PythonVersion) and (FileIsExecutable(LFile)) then + Exit(TArray.Create(LFile)); + end; + + {$WARN SYMBOL_PLATFORM OFF} + LFile := Result[High(Result)]; + if (TFileAttribute.faOwnerExecute in TFile.GetAttributes(LFile)) + or (TFileAttribute.faGroupExecute in TFile.GetAttributes(LFile)) + or (TFileAttribute.faOthersExecute in TFile.GetAttributes(LFile)) then //Avoiding symlinks + Exit(TArray.Create(LFile)); + {$WARN SYMBOL_PLATFORM ON} + + {$ENDIF POSIX} + end; +{$IFNDEF MSWINDOWS} +var + LFiles: TArray; +{$ENDIF} begin - LFiles := []; {$IFDEF MSWINDOWS} Result := TPath.Combine(GetEnvironmentPath(), 'python.exe'); if not TFile.Exists(Result) then Result := String.Empty; + {$ELSEIF DEFINED(ANDROID)} + Result := TPath.GetLibraryPath(); + LFiles := DoSearch(Result); + if LFiles <> nil then + Exit(LFiles[Low(LFiles)]); + Result := TPath.Combine(GetEnvironmentPath(), 'bin'); {$ELSE} Result := TPath.Combine(GetEnvironmentPath(), 'bin'); - LFiles := TDirectory.GetFiles(Result, 'python*', TSearchOption.soTopDirectoryOnly, - function(const Path: string; const SearchRec: TSearchRec): boolean - begin - Result := Char.IsDigit(SearchRec.Name, Length(SearchRec.Name) - 1); - end); - {$IFDEF POSIX} - for LFile in LFiles do begin - if (TPath.GetFileName(LFile) = 'python' + PythonVersion) and (FileIsExecutable(LFile)) then - Exit(LFile); - end; - {$ENDIF POSIX} + LFiles := DoSearch(Result); - {$WARN SYMBOL_PLATFORM OFF} if Length(LFiles) > 0 then begin - Result := LFiles[High(LFiles)]; - if (TFileAttribute.faOwnerExecute in TFile.GetAttributes(Result)) - or (TFileAttribute.faGroupExecute in TFile.GetAttributes(Result)) - or (TFileAttribute.faOthersExecute in TFile.GetAttributes(Result)) then //Avoiding symlinks - Exit; - {$WARN SYMBOL_PLATFORM ON} - Result := LFiles[Low(LFiles)]; if not TFile.Exists(Result) then Result := String.Empty; @@ -243,11 +274,27 @@ function TPyCustomEmbeddableDistribution.FindExecutable: string; 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 not TFile.Exists(LFile) then begin + LSearch := ALibName.Replace(TPath.GetExtension(ALibName), '') + '*' + TPath.GetExtension(ALibName); + Result := TDirectory.GetFiles( + APath, + LSearch, //Python <= 3.7 might contain a "m" as a sufix. + TSearchOption.soTopDirectoryOnly); + end else + Result := [LFile]; + end; + var I: integer; LLibName: string; LPath: string; - LSearch: string; LFiles: TArray; begin for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do @@ -258,22 +305,21 @@ function TPyCustomEmbeddableDistribution.FindSharedLibrary: string; {$IFDEF MSWINDOWS} LPath := GetEnvironmentPath(); + {$ELSEIF DEFINED(ANDROID)} + LPath := TPath.GetLibraryPath(); + LFiles := DoSearch(LLibName, LPath); + if LFiles <> nil then + Exit(LFiles[Low(LFiles)]); + LPath := GetEnvironmentPath(); {$ELSE} LPath := TPath.Combine(GetEnvironmentPath(), 'lib'); {$ENDIF} - Result := TPath.Combine(LPath, LLibName); - if not TFile.Exists(Result) then begin - LSearch := LLibName.Replace(TPath.GetExtension(LLibName), '') + '*' + TPath.GetExtension(LLibName); - LFiles := TDirectory.GetFiles( - LPath, - LSearch, //Python <= 3.7 might contain a "m" as a sufix. - TSearchOption.soTopDirectoryOnly); - if Length(LFiles) > 0 then begin - Result := LFiles[Low(LFiles)]; - end else - Result := String.Empty; - end; + LFiles := DoSearch(LLibName, LPath); + if LFiles <> nil then + Result := LFiles[Low(LFiles)] + else + Result := String.Empty; {$IFDEF LINUX} if TFile.Exists(Result + '.1.0') then //Targets directly to the so file instead of a symlink. @@ -311,18 +357,35 @@ procedure TPyCustomEmbeddableDistribution.Setup; { TPyEmbeddableDistribution } +procedure TPyEmbeddableDistribution.DoDeleteEmbeddable; +begin + TFile.Delete(EmbeddablePackage); +end; + procedure TPyEmbeddableDistribution.LoadSettings; begin if FScanned then inherited; end; +procedure TPyEmbeddableDistribution.Setup; +begin + inherited; + if FDeleteEmbeddable and EmbeddableExists() then + DoDeleteEmbeddable(); +end; + { TPyEmbeddedEnvironment } constructor TPyEmbeddedEnvironment.Create(AOwner: TComponent); begin inherited; FScanner := TScanner.Create(); + PythonVersion := PythonProject.PythonVersion; + if PythonProject.Enabled then + FScanner.ScanRule := TScanRule.srFileName + else + FScanner.ScanRule := TScanRule.srFolder; end; destructor TPyEmbeddedEnvironment.Destroy; @@ -337,10 +400,17 @@ function TPyEmbeddedEnvironment.CreateCollection: TPyDistributionCollection; end; procedure TPyEmbeddedEnvironment.Prepare; -var +var LDistribution: TPyEmbeddableDistribution; begin if FScanner.AutoScan then begin + if PythonProject.Enabled then + if FScanner.EmbeddablesPath.IsEmpty() then begin + FScanner.EmbeddablesPath := TPyEnvironmentPath.ResolvePath(TPyEnvironmentPath.DEPLOY_PATH); + FScanner.ScanRule := TScanRule.srFileName; + FScanner.DeleteEmbeddable := true; + end; + FScanner.Scan( procedure(APyVersionInfo: TPythonVersionProp; AEmbeddablePackage: string) begin if Assigned(Distributions.LocateEnvironment(APyVersionInfo.RegVersion)) then @@ -355,7 +425,11 @@ procedure TPyEmbeddedEnvironment.Prepare; APyVersionInfo.RegVersion); LDistribution.EmbeddablePackage := AEmbeddablePackage; LDistribution.OnZipProgress := FOnZipProgress; + LDistribution.DeleteEmbeddable := FScanner.DeleteEmbeddable; end); + + if PythonVersion.IsEmpty() and (Distributions.Count > 0) then + PythonVersion := TPyEmbeddableDistribution(Distributions.Items[0]).PythonVersion; end; inherited; end; @@ -373,6 +447,8 @@ procedure TPyEmbeddedEnvironment.TScanner.Scan( I: Integer; LPath: string; LFiles: TArray; + LPythonVersion: string; + LSearchPatter: string; begin if not Assigned(ACallback) then Exit; @@ -380,16 +456,31 @@ procedure TPyEmbeddedEnvironment.TScanner.Scan( if not TDirectory.Exists(FEmbeddablesPath) then raise Exception.Create('Directory not found.'); - for I := Low(PYTHON_KNOWN_VERSIONS) to High(PYTHON_KNOWN_VERSIONS) do begin - LPath := TPath.Combine(FEmbeddablesPath, PYTHON_KNOWN_VERSIONS[I].RegVersion); - if not TDirectory.Exists(LPath) then - Continue; + //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(FEmbeddablesPath, PYTHON_KNOWN_VERSIONS[I].RegVersion); + if not TDirectory.Exists(LPath) then + Continue; - LFiles := TDirectory.GetFiles(LPath, '*.zip', TSearchOption.soTopDirectoryOnly); - if (Length(LFiles) = 0) then - Continue; + LFiles := TDirectory.GetFiles(LPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); + if (Length(LFiles) = 0) then + Continue; - ACallback(PYTHON_KNOWN_VERSIONS[I], LFiles[0]); + 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(FEmbeddablesPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); + if (Length(LFiles) = 0) then + Continue; + + ACallback(PYTHON_KNOWN_VERSIONS[I], LFiles[0]); + end end; end; From 2ef9f515c9901a24fe8864c51829f570bda74b78 Mon Sep 17 00:00:00 2001 From: lmbelo Date: Sun, 3 Jul 2022 08:56:09 -0700 Subject: [PATCH 08/10] Updating projects references --- packages/P4DEnvironment.dpk | 17 +-- packages/P4DEnvironmentProject.dpk | 38 ++++++ packages/P4DEnvironmentProject.dproj | 122 ++++++++++++++++++ ...PythonEnvironmentsComponentSuite.groupproj | 58 +++++---- packages/dclP4DEnvironment.dpk | 21 +-- packages/dclP4DEnvironmentProject.dpk | 15 ++- 6 files changed, 224 insertions(+), 47 deletions(-) create mode 100644 packages/P4DEnvironmentProject.dpk create mode 100644 packages/P4DEnvironmentProject.dproj diff --git a/packages/P4DEnvironment.dpk b/packages/P4DEnvironment.dpk index 5cf1f0f..88d2c84 100644 --- a/packages/P4DEnvironment.dpk +++ b/packages/P4DEnvironment.dpk @@ -9,21 +9,21 @@ package P4DEnvironment; {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$IOCHECKS ON} -{$LOCALSYMBOLS ON} +{$LOCALSYMBOLS OFF} {$LONGSTRINGS ON} {$OPENSTRINGS ON} -{$OPTIMIZATION OFF} -{$OVERFLOWCHECKS ON} -{$RANGECHECKS ON} -{$REFERENCEINFO ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} {$SAFEDIVIDE OFF} -{$STACKFRAMES ON} +{$STACKFRAMES OFF} {$TYPEDADDRESS OFF} {$VARSTRINGCHECKS ON} {$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $400000} -{$DEFINE DEBUG} +{$DEFINE RELEASE} {$ENDIF IMPLICITBUILDING} {$LIBSUFFIX AUTO} {$RUNONLY} @@ -32,7 +32,8 @@ package P4DEnvironment; requires rtl, python, - p4dtools; + p4dtools, + p4denvironmentproject; contains PyEnvironment in '..\src\PyEnvironment.pas', diff --git a/packages/P4DEnvironmentProject.dpk b/packages/P4DEnvironmentProject.dpk new file mode 100644 index 0000000..01c9fff --- /dev/null +++ b/packages/P4DEnvironmentProject.dpk @@ -0,0 +1,38 @@ +package P4DEnvironmentProject; + +{$R *.res} +{$IFDEF IMPLICITBUILDING This IFDEF should not be used by users} +{$ALIGN 8} +{$ASSERTIONS ON} +{$BOOLEVAL OFF} +{$DEBUGINFO OFF} +{$EXTENDEDSYNTAX ON} +{$IMPORTEDDATA ON} +{$IOCHECKS ON} +{$LOCALSYMBOLS ON} +{$LONGSTRINGS ON} +{$OPENSTRINGS ON} +{$OPTIMIZATION OFF} +{$OVERFLOWCHECKS ON} +{$RANGECHECKS ON} +{$REFERENCEINFO ON} +{$SAFEDIVIDE OFF} +{$STACKFRAMES ON} +{$TYPEDADDRESS OFF} +{$VARSTRINGCHECKS ON} +{$WRITEABLECONST OFF} +{$MINENUMSIZE 1} +{$IMAGEBASE $400000} +{$DEFINE DEBUG} +{$ENDIF IMPLICITBUILDING} +{$LIBSUFFIX AUTO} +{$RUNONLY} +{$IMPLICITBUILD ON} + +requires + rtl; + +contains + PyEnvironment.Project in '..\src\Project\PyEnvironment.Project.pas'; + +end. diff --git a/packages/P4DEnvironmentProject.dproj b/packages/P4DEnvironmentProject.dproj new file mode 100644 index 0000000..a4f3b07 --- /dev/null +++ b/packages/P4DEnvironmentProject.dproj @@ -0,0 +1,122 @@ + + + True + Package + Debug + None + P4DEnvironmentProject.dpk + Win32 + {D7D611D7-E03F-4578-8243-2338F9455A17} + 19.4 + 1 + + + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + P4DEnvironmentProject + All + ..\lib\$(Platform)\$(Config) + .\$(Platform)\$(Config) + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(Auto) + true + true + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + + + Debug + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + + + true + true + DEBUG;$(DCC_Define) + true + true + false + true + true + + + false + true + 1033 + + + 0 + RELEASE;$(DCC_Define) + false + 0 + + + + MainSource + + + + + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + + + + Delphi.Personality.12 + Package + + + + P4DEnvironmentProject.dpk + + + + + False + False + False + False + False + True + False + False + + + 12 + + + + + diff --git a/packages/P4DPythonEnvironmentsComponentSuite.groupproj b/packages/P4DPythonEnvironmentsComponentSuite.groupproj index e809023..60160df 100644 --- a/packages/P4DPythonEnvironmentsComponentSuite.groupproj +++ b/packages/P4DPythonEnvironmentsComponentSuite.groupproj @@ -6,14 +6,17 @@ - - - - + - + P4DEnvironmentProject.dproj + + + P4DTools.dproj;P4DEnvironmentProject.dproj + + + P4DEnvironment.dproj @@ -32,41 +35,50 @@ - + + + + + + + + + + + + + + + + + + + - + - + - + - + - + - - - - - - - - - - + - + - + diff --git a/packages/dclP4DEnvironment.dpk b/packages/dclP4DEnvironment.dpk index db6a81f..42c8f9b 100644 --- a/packages/dclP4DEnvironment.dpk +++ b/packages/dclP4DEnvironment.dpk @@ -10,21 +10,21 @@ package dclP4DEnvironment; {$EXTENDEDSYNTAX ON} {$IMPORTEDDATA ON} {$IOCHECKS ON} -{$LOCALSYMBOLS ON} +{$LOCALSYMBOLS OFF} {$LONGSTRINGS ON} {$OPENSTRINGS ON} -{$OPTIMIZATION OFF} -{$OVERFLOWCHECKS ON} -{$RANGECHECKS ON} -{$REFERENCEINFO ON} +{$OPTIMIZATION ON} +{$OVERFLOWCHECKS OFF} +{$RANGECHECKS OFF} +{$REFERENCEINFO OFF} {$SAFEDIVIDE OFF} -{$STACKFRAMES ON} +{$STACKFRAMES OFF} {$TYPEDADDRESS OFF} {$VARSTRINGCHECKS ON} {$WRITEABLECONST OFF} {$MINENUMSIZE 1} {$IMAGEBASE $400000} -{$DEFINE DEBUG} +{$DEFINE RELEASE} {$ENDIF IMPLICITBUILDING} {$DESCRIPTION 'P4D AI&ML - Python environments'} {$LIBSUFFIX AUTO} @@ -33,9 +33,12 @@ package dclP4DEnvironment; requires rtl, - p4denvironment; + p4denvironment, + designide, + dclp4denvironmentproject; contains - PyEnvironment.Registration in '..\src\PyEnvironment.Registration.pas'; + PyEnvironment.Registration in '..\src\PyEnvironment.Registration.pas', + PyEnvionment.Editors in '..\src\PyEnvionment.Editors.pas'; end. diff --git a/packages/dclP4DEnvironmentProject.dpk b/packages/dclP4DEnvironmentProject.dpk index 3b444f3..42857b1 100644 --- a/packages/dclP4DEnvironmentProject.dpk +++ b/packages/dclP4DEnvironmentProject.dpk @@ -32,15 +32,16 @@ package dclP4DEnvironmentProject; requires rtl, - designide; + designide, + P4DEnvironmentProject; contains - PyEnvironment.Project.Helper in '..\src\Project\PyEnvironment.Project.Helper.pas', - PyEnvironment.Project.ManagerMenu in '..\src\Project\PyEnvironment.Project.ManagerMenu.pas', - PyEnvironment.Project.Menu in '..\src\Project\PyEnvironment.Project.Menu.pas', - PyEnvironment.Project in '..\src\Project\PyEnvironment.Project.pas', - PyEnvironment.Project.Types in '..\src\Project\PyEnvironment.Project.Types.pas', - PyEnvironment.Project.Registration in '..\src\Project\PyEnvironment.Project.Registration.pas'; + 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.Registration in '..\src\Project\IDE\PyEnvironment.Project.IDE.Registration.pas', + PyEnvironment.Project.IDE.Types in '..\src\Project\IDE\PyEnvironment.Project.IDE.Types.pas'; end. From 19c759c6d42cc7fa14f653b571ff21ac3e6ba97f Mon Sep 17 00:00:00 2001 From: lmbelo Date: Wed, 27 Jul 2022 06:28:42 -0700 Subject: [PATCH 09/10] Python setup and activate enhancements --- src/AddOn/PyEnvironment.AddOn.EnsurePip.pas | 1 + src/Embeddable/PyEnvironment.Embeddable.pas | 33 +++++++++------------ src/PyEnvironment.pas | 5 ++-- 3 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas b/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas index 60c8d10..de06d37 100644 --- a/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas +++ b/src/AddOn/PyEnvironment.AddOn.EnsurePip.pas @@ -77,6 +77,7 @@ procedure TPyEnvironmentAddOnEnsurePip.InternalExecute( LOutput: string; begin inherited; + { TODO : Check for valida executable and shared library files } if (TExecCmdService.Cmd(ADistribution.Executable, TExecCmdArgs.BuildArgv( ADistribution.Executable, ['-m', 'pip', '--version']), diff --git a/src/Embeddable/PyEnvironment.Embeddable.pas b/src/Embeddable/PyEnvironment.Embeddable.pas index 7f0557c..bcc068e 100644 --- a/src/Embeddable/PyEnvironment.Embeddable.pas +++ b/src/Embeddable/PyEnvironment.Embeddable.pas @@ -127,7 +127,7 @@ TScanner = class(TPersistent) FEnvironmentPath: string; FDeleteEmbeddable: boolean; public - procedure Scan(ACallback: TProc); + procedure Scan(const AEmbedabblesPath: string; ACallback: TProc); published property AutoScan: boolean read FAutoScan write FAutoScan default false; property ScanRule: TScanRule read FScanRule write FScanRule; @@ -207,6 +207,7 @@ 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)); @@ -233,15 +234,6 @@ function TPyCustomEmbeddableDistribution.FindExecutable: string; if (TPath.GetFileName(LFile) = 'python' + PythonVersion) and (FileIsExecutable(LFile)) then Exit(TArray.Create(LFile)); end; - - {$WARN SYMBOL_PLATFORM OFF} - LFile := Result[High(Result)]; - if (TFileAttribute.faOwnerExecute in TFile.GetAttributes(LFile)) - or (TFileAttribute.faGroupExecute in TFile.GetAttributes(LFile)) - or (TFileAttribute.faOthersExecute in TFile.GetAttributes(LFile)) then //Avoiding symlinks - Exit(TArray.Create(LFile)); - {$WARN SYMBOL_PLATFORM ON} - {$ENDIF POSIX} end; {$IFNDEF MSWINDOWS} @@ -252,8 +244,9 @@ function TPyCustomEmbeddableDistribution.FindExecutable: string; {$IFDEF MSWINDOWS} Result := TPath.Combine(GetEnvironmentPath(), 'python.exe'); if not TFile.Exists(Result) then - Result := String.Empty; + Exit(String.Empty); {$ELSEIF DEFINED(ANDROID)} + //Let's try it in the library path first - we should place it in the library path in Android Result := TPath.GetLibraryPath(); LFiles := DoSearch(Result); if LFiles <> nil then @@ -261,16 +254,15 @@ function TPyCustomEmbeddableDistribution.FindExecutable: string; Result := TPath.Combine(GetEnvironmentPath(), 'bin'); {$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; - {$ENDIF} end; function TPyCustomEmbeddableDistribution.FindSharedLibrary: string; @@ -306,11 +298,13 @@ function TPyCustomEmbeddableDistribution.FindSharedLibrary: string; {$IFDEF MSWINDOWS} LPath := GetEnvironmentPath(); {$ELSEIF DEFINED(ANDROID)} + //Let's try it in the library path first - we should place it in the library path in Android LPath := TPath.GetLibraryPath(); LFiles := DoSearch(LLibName, LPath); if LFiles <> nil then Exit(LFiles[Low(LFiles)]); - LPath := GetEnvironmentPath(); + //Try to find it in the environments path + LPath := TPath.Combine(GetEnvironmentPath(), 'lib'); {$ELSE} LPath := TPath.Combine(GetEnvironmentPath(), 'lib'); {$ENDIF} @@ -406,12 +400,13 @@ procedure TPyEmbeddedEnvironment.Prepare; if FScanner.AutoScan then begin if PythonProject.Enabled then if FScanner.EmbeddablesPath.IsEmpty() then begin - FScanner.EmbeddablesPath := TPyEnvironmentPath.ResolvePath(TPyEnvironmentPath.DEPLOY_PATH); + FScanner.EmbeddablesPath := TPyEnvironmentPath.DEPLOY_PATH; FScanner.ScanRule := TScanRule.srFileName; FScanner.DeleteEmbeddable := true; end; FScanner.Scan( + TPyEnvironmentPath.ResolvePath(FScanner.EmbeddablesPath), procedure(APyVersionInfo: TPythonVersionProp; AEmbeddablePackage: string) begin if Assigned(Distributions.LocateEnvironment(APyVersionInfo.RegVersion)) then Exit; @@ -441,7 +436,7 @@ procedure TPyEmbeddedEnvironment.SetScanner(const Value: TScanner); { TPyEmbeddedEnvironment.TScanner } -procedure TPyEmbeddedEnvironment.TScanner.Scan( +procedure TPyEmbeddedEnvironment.TScanner.Scan(const AEmbedabblesPath: string; ACallback: TProc); var I: Integer; @@ -453,14 +448,14 @@ procedure TPyEmbeddedEnvironment.TScanner.Scan( if not Assigned(ACallback) then Exit; - if not TDirectory.Exists(FEmbeddablesPath) then + if not TDirectory.Exists(AEmbedabblesPath) then raise Exception.Create('Directory not found.'); //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(FEmbeddablesPath, PYTHON_KNOWN_VERSIONS[I].RegVersion); + LPath := TPath.Combine(AEmbedabblesPath, PYTHON_KNOWN_VERSIONS[I].RegVersion); if not TDirectory.Exists(LPath) then Continue; @@ -475,7 +470,7 @@ procedure TPyEmbeddedEnvironment.TScanner.Scan( 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(FEmbeddablesPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); + LFiles := TDirectory.GetFiles(AEmbedabblesPath, LSearchPatter, TSearchOption.soTopDirectoryOnly); if (Length(LFiles) = 0) then Continue; diff --git a/src/PyEnvironment.pas b/src/PyEnvironment.pas index 016ebef..5a27afb 100644 --- a/src/PyEnvironment.pas +++ b/src/PyEnvironment.pas @@ -173,8 +173,7 @@ procedure TPyCustomEnvironment.Notification(AComponent: TComponent; procedure TPyCustomEnvironment.Setup(APythonVersion: string); begin - InternalSetup( - IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion)); + InternalSetup(APythonVersion); end; function TPyCustomEnvironment.SetupAsync(APythonVersion: string): ITask; @@ -325,6 +324,8 @@ procedure TPyCustomEnvironment.InternalSetup(APythonVersion: string); Prepare(); + APythonVersion := IfThen(APythonVersion.IsEmpty(), PythonVersion, APythonVersion); + LDistribution := FDistributions.LocateEnvironment(APythonVersion); if not Assigned(LDistribution) then Exit(); From 9b53be7daf5c4e572537b601b11228b83ebbfe44 Mon Sep 17 00:00:00 2001 From: lmbelo Date: Wed, 27 Jul 2022 06:55:01 -0700 Subject: [PATCH 10/10] Including .res files to the gitignore --- .gitignore | 2 +- packages/P4DEnvironment.dproj | 17 +- packages/P4DEnvironmentProject.dproj | 62 +- packages/P4DTools.dproj | 2 +- packages/dclP4DEnvironment.dproj | 3 + packages/dclP4DEnvironmentProject.dproj | 835 +----------------------- 6 files changed, 106 insertions(+), 815 deletions(-) diff --git a/.gitignore b/.gitignore index 9532800..6670a78 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ # # Resource files are binaries containing manifest, project icon and version info. # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. -#*.res +*.res # # Type library file (binary). In old Delphi versions it should be stored. # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. diff --git a/packages/P4DEnvironment.dproj b/packages/P4DEnvironment.dproj index 31f5e0d..080227d 100644 --- a/packages/P4DEnvironment.dproj +++ b/packages/P4DEnvironment.dproj @@ -5,7 +5,7 @@ Release None P4DEnvironment.dpk - Win64 + Win32 {08C84E96-1508-43E8-87F7-A4B2F8AF2205} 19.4 168083 @@ -71,6 +71,12 @@ Base true + + true + Cfg_2 + true + true + P4DEnvironment ..\resources @@ -138,6 +144,8 @@ false + C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe + C:\Users\lucas\OneDrive\Documents\Embarcadero\Studio\Projects\deployabletest\PyDeploy.dpr true 1033 @@ -147,6 +155,12 @@ false 0 + + C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe + C:\Users\lucas\OneDrive\Documents\Embarcadero\Studio\Projects\deployabletest\PyDeploy.dpr + true + 1033 + MainSource @@ -154,6 +168,7 @@ + diff --git a/packages/P4DEnvironmentProject.dproj b/packages/P4DEnvironmentProject.dproj index a4f3b07..9bfee5a 100644 --- a/packages/P4DEnvironmentProject.dproj +++ b/packages/P4DEnvironmentProject.dproj @@ -2,22 +2,47 @@ True Package - Debug + Release None P4DEnvironmentProject.dpk Win32 {D7D611D7-E03F-4578-8243-2338F9455A17} 19.4 - 1 + 168083 true + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + true Base true + + true + Base + true + true Base @@ -47,6 +72,22 @@ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 1033 + + Debug + 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 + 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 + 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 + + + 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 + Debug Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) @@ -54,6 +95,11 @@ CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 1033 + + Debug + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + true + true true @@ -104,13 +150,13 @@ - False - False - False - False - False + True + True + True + True + True True - False + True False diff --git a/packages/P4DTools.dproj b/packages/P4DTools.dproj index e6445b6..ea58d03 100644 --- a/packages/P4DTools.dproj +++ b/packages/P4DTools.dproj @@ -5,7 +5,7 @@ Release None P4DTools.dpk - Win64 + Win32 {ACD248FC-DD28-4E84-9959-B3E7F13EF694} 19.4 168083 diff --git a/packages/dclP4DEnvironment.dproj b/packages/dclP4DEnvironment.dproj index 7d36329..46e1dc9 100644 --- a/packages/dclP4DEnvironment.dproj +++ b/packages/dclP4DEnvironment.dproj @@ -94,7 +94,10 @@ + + + RCDATA TPYEMBEDDEDENVIRONMENT128_PNG diff --git a/packages/dclP4DEnvironmentProject.dproj b/packages/dclP4DEnvironmentProject.dproj index c232921..a84de81 100644 --- a/packages/dclP4DEnvironmentProject.dproj +++ b/packages/dclP4DEnvironmentProject.dproj @@ -1,33 +1,18 @@  - {4FE390C6-126B-4D5E-B1DF-8624DAB3A6CE} - dclP4DEnvironmentProject.dpk - 19.4 - None True - Debug + Package + Release + None + dclP4DEnvironmentProject.dpk Win32 + {4FE390C6-126B-4D5E-B1DF-8624DAB3A6CE} + 19.4 1 - Package true - - true - Base - true - - - true - Base - true - - - true - Base - true - true Base @@ -50,63 +35,48 @@ true + dclP4DEnvironmentProject + All ..\lib\$(Platform)\$(Config) + P4D Environments IDE Extension .\$(Platform)\$(Config) - false - false - false - false - false - true - true System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) - All - 1033 - CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= - P4D Environments IDE Extension true $(Auto) - dclP4DEnvironmentProject - - - None - annotation-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.0.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.0.1.dex.jar;core-runtime-2.0.1.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.0.0.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.0.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.0.0.dex.jar;lifecycle-runtime-2.0.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.0.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar - - - None - annotation-1.2.0.dex.jar;asynclayoutinflater-1.0.0.dex.jar;billing-4.0.0.dex.jar;browser-1.0.0.dex.jar;cloud-messaging.dex.jar;collection-1.0.0.dex.jar;coordinatorlayout-1.0.0.dex.jar;core-1.5.0-rc02.dex.jar;core-common-2.0.1.dex.jar;core-runtime-2.0.1.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;firebase-annotations-16.0.0.dex.jar;firebase-common-20.0.0.dex.jar;firebase-components-17.0.0.dex.jar;firebase-datatransport-18.0.0.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.0.0.dex.jar;firebase-installations-interop-17.0.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-22.0.0.dex.jar;fmx.dex.jar;fragment-1.0.0.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;legacy-support-core-ui-1.0.0.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.0.0.dex.jar;lifecycle-livedata-2.0.0.dex.jar;lifecycle-livedata-core-2.0.0.dex.jar;lifecycle-runtime-2.0.0.dex.jar;lifecycle-service-2.0.0.dex.jar;lifecycle-viewmodel-2.0.0.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;play-services-ads-20.1.0.dex.jar;play-services-ads-base-20.1.0.dex.jar;play-services-ads-identifier-17.0.0.dex.jar;play-services-ads-lite-20.1.0.dex.jar;play-services-base-17.5.0.dex.jar;play-services-basement-17.6.0.dex.jar;play-services-cloud-messaging-16.0.0.dex.jar;play-services-drive-17.0.0.dex.jar;play-services-games-21.0.0.dex.jar;play-services-location-18.0.0.dex.jar;play-services-maps-17.0.1.dex.jar;play-services-measurement-base-18.0.0.dex.jar;play-services-measurement-sdk-api-18.0.0.dex.jar;play-services-places-placereport-17.0.0.dex.jar;play-services-stats-17.0.0.dex.jar;play-services-tasks-17.2.0.dex.jar;print-1.0.0.dex.jar;room-common-2.1.0.dex.jar;room-runtime-2.1.0.dex.jar;slidingpanelayout-1.0.0.dex.jar;sqlite-2.0.1.dex.jar;sqlite-framework-2.0.1.dex.jar;swiperefreshlayout-1.0.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.0.0.dex.jar;transport-runtime-3.0.0.dex.jar;user-messaging-platform-1.0.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.1.0.dex.jar - - - None + true + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 - Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) Debug + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + rtl;$(DCC_UsePackage) true CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= 1033 - DEBUG;$(DCC_Define) true - false - true true - true + DEBUG;$(DCC_Define) + true true + false true + true false - true C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe C:\Users\lucas\OneDrive\Documents\Embarcadero\Studio\Projects\deployabletest\Project1.dpr + true - false + 0 RELEASE;$(DCC_Define) + false 0 - 0 @@ -114,12 +84,13 @@ - - - - - - + + + + + + + Base @@ -137,764 +108,20 @@ Package - - P4D Environments IDE Extension - Embarcadero C++Builder Office 2000 Servers Package - Embarcadero C++Builder Office XP Servers Package - Microsoft Office 2000 Sample Automation Server Wrapper Components - Microsoft Office XP Sample Automation Server Wrapper Components - dclP4DEnvironmentProject.dpk + - - - - true - - - - - true - - - - - true - - - - - dclP4DEnvironmentProject.bpl - true - - - - - 1 - - - 0 - - - - - classes - 64 - - - classes - 64 - - - - - res\xml - 1 - - - res\xml - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\armeabi - 1 - - - library\lib\armeabi - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - library\lib\mips - 1 - - - library\lib\mips - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - - - library\lib\armeabi-v7a - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\values-v21 - 1 - - - res\values-v21 - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - res\drawable - 1 - - - res\drawable - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-ldpi - 1 - - - res\drawable-ldpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-mdpi - 1 - - - res\drawable-mdpi - 1 - - - - - res\drawable-hdpi - 1 - - - res\drawable-hdpi - 1 - - - - - res\drawable-xhdpi - 1 - - - res\drawable-xhdpi - 1 - - - - - res\drawable-xxhdpi - 1 - - - res\drawable-xxhdpi - 1 - - - - - res\drawable-xxxhdpi - 1 - - - res\drawable-xxxhdpi - 1 - - - - - res\drawable-small - 1 - - - res\drawable-small - 1 - - - - - res\drawable-normal - 1 - - - res\drawable-normal - 1 - - - - - res\drawable-large - 1 - - - res\drawable-large - 1 - - - - - res\drawable-xlarge - 1 - - - res\drawable-xlarge - 1 - - - - - res\values - 1 - - - res\values - 1 - - - - - 1 - - - 1 - - - 0 - - - - - 1 - .framework - - - 1 - .framework - - - 1 - .framework - - - 0 - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .dll;.bpl - - - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 1 - .dylib - - - 0 - .bpl - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset - 1 - - - - - 1 - - - 1 - - - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF - 1 - - - - - - - - 1 - - - 1 - - - 1 - - - - - - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - Contents\Resources - 1 - - - - - library\lib\armeabi-v7a - 1 - - - library\lib\arm64-v8a - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 1 - - - 0 - - - - - library\lib\armeabi-v7a - 1 - - - - - 1 - - - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - Assets - 1 - - - Assets - 1 - - - - - - - - - - - - - - False False - False False False False True False + False 12