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.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/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.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..9bfee5a
--- /dev/null
+++ b/packages/P4DEnvironmentProject.dproj
@@ -0,0 +1,168 @@
+
+
+ True
+ Package
+ Release
+ None
+ P4DEnvironmentProject.dpk
+ Win32
+ {D7D611D7-E03F-4578-8243-2338F9455A17}
+ 19.4
+ 168083
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ 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
+ 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)
+ 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;$(DCC_Namespace)
+ true
+
+
+ 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
+
+
+
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ False
+
+
+ 12
+
+
+
+
+
diff --git a/packages/P4DPythonEnvironmentsComponentSuite.groupproj b/packages/P4DPythonEnvironmentsComponentSuite.groupproj
index 1170405..60160df 100644
--- a/packages/P4DPythonEnvironmentsComponentSuite.groupproj
+++ b/packages/P4DPythonEnvironmentsComponentSuite.groupproj
@@ -6,11 +6,17 @@
-
+
+
+ P4DEnvironmentProject.dproj
+
+
+ P4DTools.dproj;P4DEnvironmentProject.dproj
+
-
+ P4DEnvironment.dproj
@@ -29,32 +35,50 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
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.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/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.dpk b/packages/dclP4DEnvironmentProject.dpk
new file mode 100644
index 0000000..42857b1
--- /dev/null
+++ b/packages/dclP4DEnvironmentProject.dpk
@@ -0,0 +1,47 @@
+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,
+ P4DEnvironmentProject;
+
+contains
+ PyEnvironment.Project.IDE.Helper in '..\src\Project\IDE\PyEnvironment.Project.IDE.Helper.pas',
+ PyEnvironment.Project.IDE.ManagerMenu in '..\src\Project\IDE\PyEnvironment.Project.IDE.ManagerMenu.pas',
+ PyEnvironment.Project.IDE.Menu in '..\src\Project\IDE\PyEnvironment.Project.IDE.Menu.pas',
+ PyEnvironment.Project.IDE.Deploy in '..\src\Project\IDE\PyEnvironment.Project.IDE.Deploy.pas',
+ PyEnvironment.Project.IDE.Registration in '..\src\Project\IDE\PyEnvironment.Project.IDE.Registration.pas',
+ PyEnvironment.Project.IDE.Types in '..\src\Project\IDE\PyEnvironment.Project.IDE.Types.pas';
+
+end.
+
diff --git a/packages/dclP4DEnvironmentProject.dproj b/packages/dclP4DEnvironmentProject.dproj
new file mode 100644
index 0000000..a84de81
--- /dev/null
+++ b/packages/dclP4DEnvironmentProject.dproj
@@ -0,0 +1,132 @@
+
+
+ True
+ Package
+ Release
+ None
+ dclP4DEnvironmentProject.dpk
+ Win32
+ {4FE390C6-126B-4D5E-B1DF-8624DAB3A6CE}
+ 19.4
+ 1
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ dclP4DEnvironmentProject
+ All
+ ..\lib\$(Platform)\$(Config)
+ P4D Environments IDE Extension
+ .\$(Platform)\$(Config)
+ System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)
+ true
+ $(Auto)
+ 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)
+ 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
+
+
+ true
+ true
+ DEBUG;$(DCC_Define)
+ true
+ true
+ false
+ true
+ true
+
+
+ false
+ C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe
+ C:\Users\lucas\OneDrive\Documents\Embarcadero\Studio\Projects\deployabletest\Project1.dpr
+ true
+
+
+ 0
+ RELEASE;$(DCC_Define)
+ false
+ 0
+
+
+
+ MainSource
+
+
+
+
+
+
+
+
+
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+ Cfg_2
+ Base
+
+
+
+ Delphi.Personality.12
+ Package
+
+
+
+ dclP4DEnvironmentProject.dpk
+
+
+
+
+ False
+ False
+ False
+ False
+ False
+ True
+ False
+ False
+
+
+ 12
+
+
+
+
+
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 1219fd0..bcc068e 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);
+ procedure Scan(const AEmbedabblesPath: string; ACallback: TProc);
published
property AutoScan: boolean read FAutoScan write FAutoScan default false;
+ property ScanRule: TScanRule read FScanRule write FScanRule;
property EmbeddablesPath: string read FEmbeddablesPath write FEmbeddablesPath;
///
/// Default environment path.
///
property EnvironmentPath: string read FEnvironmentPath write FEnvironmentPath;
+ ///
+ /// Delete the embeddable zip file after install.
+ ///
+ property DeleteEmbeddable: boolean read FDeleteEmbeddable write FDeleteEmbeddable;
end;
private
FScanner: TScanner;
@@ -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}
@@ -191,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));
@@ -199,55 +216,77 @@ 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;
+ {$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;
+ 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
+ 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}
+ {$ENDIF}
- {$WARN SYMBOL_PLATFORM OFF}
+ LFiles := DoSearch(Result);
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;
end else
Result := String.Empty;
- {$ENDIF}
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 +297,23 @@ 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)]);
+ //Try to find it in the environments path
+ LPath := TPath.Combine(GetEnvironmentPath(), 'lib');
{$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 +351,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,11 +394,19 @@ 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.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;
@@ -355,7 +420,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;
@@ -367,29 +436,46 @@ procedure TPyEmbeddedEnvironment.SetScanner(const Value: TScanner);
{ TPyEmbeddedEnvironment.TScanner }
-procedure TPyEmbeddedEnvironment.TScanner.Scan(
+procedure TPyEmbeddedEnvironment.TScanner.Scan(const AEmbedabblesPath: string;
ACallback: TProc);
var
I: Integer;
LPath: string;
LFiles: TArray;
+ LPythonVersion: string;
+ LSearchPatter: string;
begin
if not Assigned(ACallback) then
Exit;
- if not TDirectory.Exists(FEmbeddablesPath) then
+ if not TDirectory.Exists(AEmbedabblesPath) 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(AEmbedabblesPath, 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(AEmbedabblesPath, LSearchPatter, TSearchOption.soTopDirectoryOnly);
+ if (Length(LFiles) = 0) then
+ Continue;
+
+ ACallback(PYTHON_KNOWN_VERSIONS[I], LFiles[0]);
+ end
end;
end;
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/Project/PyEnvironment.Project.pas b/src/Project/PyEnvironment.Project.pas
new file mode 100644
index 0000000..f38c93c
--- /dev/null
+++ b/src/Project/PyEnvironment.Project.pas
@@ -0,0 +1,65 @@
+unit PyEnvironment.Project;
+
+interface
+
+uses
+ System.Classes;
+
+type
+ TPyEnvironmentProject = class
+ private
+ function GetPythonVersion: string;
+ function GetEnabled: boolean;
+ private
+ class constructor Create();
+ class destructor Destory();
+ public
+ property Enabled: boolean read GetEnabled;
+ property PythonVersion: string read GetPythonVersion;
+ end;
+
+var
+ PythonProject: TPyEnvironmentProject;
+
+implementation
+
+uses
+ System.SysUtils, System.Rtti;
+
+{ TPyEnvironmentProject }
+
+class constructor TPyEnvironmentProject.Create;
+begin
+ PythonProject := TPyEnvironmentProject.Create();
+end;
+
+class destructor TPyEnvironmentProject.Destory;
+begin
+ FreeAndNil(PythonProject);
+end;
+
+function TPyEnvironmentProject.GetEnabled: boolean;
+begin
+ {$IFDEF PYTHON}
+ Result := true;
+ {$ELSE}
+ Result := false;
+ {$ENDIF}
+end;
+
+function TPyEnvironmentProject.GetPythonVersion: string;
+begin
+ {$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.
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.
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;
diff --git a/src/PyEnvironment.pas b/src/PyEnvironment.pas
index 5526c59..5a27afb 100644
--- a/src/PyEnvironment.pas
+++ b/src/PyEnvironment.pas
@@ -96,10 +96,10 @@ TPyCustomEnvironment = class(TComponent, IEnvironmentNotifier