diff --git a/LICENSE.txt b/LICENSE.txt index be6ba56..2c5e9e4 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (c) 2024, Perforce Software, Inc. All rights reserved. +Copyright (c) 2025, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -32,7 +32,7 @@ this program. P4/P4API License ----------------------- -Copyright (c) 1995-2024, Perforce Software, Inc. +Copyright (c) 1995-2025, Perforce Software, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/P4.py b/P4.py index d605a9a..9656775 100755 --- a/P4.py +++ b/P4.py @@ -7,7 +7,7 @@ This uses the Python type P4API.P4Adapter, which is a wrapper for the Perforce ClientApi object. - $Id: //depot/main/p4-python/P4.py#112 $ + $Id: //depot/main/p4-python/P4.py#115 $ #******************************************************************************* # Copyright (c) 2007-2010, Perforce Software, Inc. All rights reserved. @@ -59,24 +59,34 @@ class P4Exception(Exception): def __init__(self, value): super().__init__(value) if isinstance(value, (list, tuple)) and len(value) > 2: + self.value = value[0] + self.warnings = value[2] if len(value[1]) > 0 or len(value[2]) > 0: - self.value = value[0] self.errors = value[1] - self.warnings = value[2] else: - self.value = value[0] self.errors = [re.sub(r'\[.*?\] ', '', str(self.value).split("\n")[0])] - self.warnings = value[2] else: self.value = value + self.errors =self.warnings = None def __str__(self): if self.errors: - return str(self.errors[0]) + if isinstance(self.errors, (list, tuple)): + return str(self.errors[0]) + else: + return str(self.errors) elif self.warnings: - return str(self.warnings[0]) + if isinstance(self.warnings, (list, tuple)): + return str(self.warnings[0]) + else: + return str(self.warnings) + elif self.errors is None and self.warnings is None: + return str(self.value) else: - return re.sub(r'\[.*?\] ', '', str(self.value).split("\n")[0]) + if isinstance(self.value, (list, tuple)): + return re.sub(r'\[.*?\] ', '', str(self.value).split("\n")[0]) + else: + return str(self.value) def __repr__(self): return f"{self.__class__.__name__}({str(self)!r})" @@ -615,7 +625,7 @@ def run(self, *args, **kargs): flatArgs = self.__flatten(args) if self.logger: - self.logger.info("p4 " + " ".join(flatArgs)) + self.logger.info("p4 " + " ".join(str(x) for x in flatArgs)) # if encoding is set, translate to Bytes if hasattr(self,"encoding") and self.encoding and not self.encoding == 'raw': @@ -752,28 +762,44 @@ def run_print(self, *args, **kargs): result = [] if raw: debugResult = [] + file_contents = None + + def flush_file_contents(): + nonlocal file_contents + if file_contents is None: + return + elif len(file_contents) == 0: + # If no file contents, assume string (text) + result.append("") + else: + # Ensure all elements in file_contents are of the same type + content_type = type(file_contents[0]) + if any(not isinstance(x, content_type) for x in file_contents): + raise TypeError("Mixed types in file content.") + if content_type is bytes: + result.append(b"".join(file_contents)) + else: + result.append("".join(file_contents)) + file_contents = None + for line in raw: if isinstance(line, dict): + flush_file_contents() result.append(line) if logger: debugResult.append(line) - result.append("") + file_contents = [] else: - # to support encoding for Python 3, we have to do a little dance - # we cannot add bytes to the str "", but we expect that all lines - # are either str or bytes. So if we encounter bytes, we replace the content - try: - result[-1] += line - except TypeError: - if type(line) == bytes and type(result[-1]) == str and result[-1] == "": - result[-1] = line - else: - raise + if file_contents is None: + file_contents = [] + file_contents.append(line) + + flush_file_contents() + if logger: logger.debug(debugResult) - return result - else: - return [] + + return result def run_resolve(self, *args, **kargs): if self.resolver: diff --git a/P4API.cpp b/P4API.cpp index 0d1c060..f14e046 100644 --- a/P4API.cpp +++ b/P4API.cpp @@ -27,7 +27,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: //depot/main/p4-python/P4API.cpp#64 $ + * $Id: //depot/main/p4-python/P4API.cpp#65 $ * * Build instructions: * Use Distutils - see accompanying setup.py @@ -338,7 +338,7 @@ static PyObject * P4API_dvcs_init(P4Adapter * self, PyObject * args, PyObject * return NULL; - auto_ptr personalServer( create_server(user, client, directory, &ui) ); + unique_ptr personalServer( create_server(user, client, directory, &ui) ); if( personalServer.get() == NULL) return NULL; @@ -400,7 +400,7 @@ static PyObject * P4API_dvcs_clone(P4Adapter * self, PyObject * args, PyObject * &progress)) return NULL; - auto_ptr personalServer( create_server(user, client, directory, &ui) ); + unique_ptr personalServer( create_server(user, client, directory, &ui) ); if( personalServer.get() == NULL) return NULL; diff --git a/PythonClientAPI.cpp b/PythonClientAPI.cpp index da10f29..53bddfb 100644 --- a/PythonClientAPI.cpp +++ b/PythonClientAPI.cpp @@ -25,7 +25,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -$Id: //depot/main/p4-python/PythonClientAPI.cpp#79 $ +$Id: //depot/main/p4-python/PythonClientAPI.cpp#81 $ *******************************************************************************/ #include @@ -75,6 +75,8 @@ PythonClientAPI::PythonClientAPI() maxResults = 0; maxScanRows = 0; maxLockTime = 0; + maxOpenFiles = 0; + maxMemory = 0; prog = "unnamed p4-python script"; apiLevel = atoi( P4Tag::l_client ); enviro = new Enviro; @@ -157,6 +159,8 @@ PythonClientAPI::intattribute_t PythonClientAPI::intattributes[] = { { "maxresults", &PythonClientAPI::SetMaxResults, &PythonClientAPI::GetMaxResults }, { "maxscanrows", &PythonClientAPI::SetMaxScanRows, &PythonClientAPI::GetMaxScanRows }, { "maxlocktime", &PythonClientAPI::SetMaxLockTime, &PythonClientAPI::GetMaxLockTime }, + { "maxopenfiles", &PythonClientAPI::SetMaxOpenFiles, &PythonClientAPI::GetMaxOpenFiles }, + { "maxmemory", &PythonClientAPI::SetMaxMemory, &PythonClientAPI::GetMaxMemory }, { "exception_level", &PythonClientAPI::SetExceptionLevel, &PythonClientAPI::GetExceptionLevel }, { "debug", &PythonClientAPI::SetDebug, &PythonClientAPI::GetDebug }, { "track", &PythonClientAPI::SetTrack, &PythonClientAPI::GetTrack }, @@ -703,7 +707,12 @@ PyObject * PythonClientAPI::ConnectOrReconnect() Error e; ResetFlags(); - client.Init( &e ); + + { + ReleasePythonLock guard; // Release GIL during connect + client.Init( &e ); + } + if ( e.Test() && exceptionLevel ) { Except( "P4.connect()", &e ); return NULL; @@ -747,7 +756,12 @@ PyObject * PythonClientAPI::Disconnect() } Error e; - client.Final( &e ); + + { + ReleasePythonLock guard; // Release GIL during disconnect + client.Final( &e ); + } + ResetFlags(); // Clear the specdef cache. @@ -1234,6 +1248,8 @@ void PythonClientAPI::RunCmd(const char *cmd, ClientUser *ui, int argc, char * c if( maxResults ) client.SetVar( "maxResults", maxResults ); if( maxScanRows ) client.SetVar( "maxScanRows", maxScanRows ); if( maxLockTime ) client.SetVar( "maxLockTime", maxLockTime ); + if( maxOpenFiles ) client.SetVar( "maxOpenFiles", maxOpenFiles ); + if( maxMemory) client.SetVar( "maxMemory", maxMemory ); // if progress is set, set the progress var if( ((PythonClientUser*)ui)->GetProgress() != Py_None ) diff --git a/PythonClientAPI.h b/PythonClientAPI.h index 0e10343..e82fc29 100644 --- a/PythonClientAPI.h +++ b/PythonClientAPI.h @@ -26,7 +26,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: //depot/main/p4-python/PythonClientAPI.h#44 $ + * $Id: //depot/main/p4-python/PythonClientAPI.h#45 $ * * Build instructions: * Use Distutils - see accompanying setup.py @@ -103,6 +103,8 @@ class PythonClientAPI int SetMaxResults( int v ) { maxResults = v; return 0; } int SetMaxScanRows( int v ) { maxScanRows = v; return 0; } int SetMaxLockTime( int v ) { maxLockTime = v; return 0; } + int SetMaxOpenFiles( int v ) { maxOpenFiles = v; return 0; } + int SetMaxMemory( int v ) { maxMemory = v; return 0; } int SetCaseFolding( int v ) { StrPtr::SetCaseFolding((StrPtr::CaseUse) v); return 0;} @@ -154,6 +156,8 @@ class PythonClientAPI int GetMaxResults() { return maxResults; } int GetMaxScanRows() { return maxScanRows; } int GetMaxLockTime() { return maxLockTime; } + int GetMaxOpenFiles() { return maxOpenFiles; } + int GetMaxMemory() { return maxMemory; } int GetDebug() { return debug.getDebug(); } int GetApiLevel() { return apiLevel; } int GetCaseFolding() { return (int) StrPtr::CaseUsage(); } @@ -329,6 +333,8 @@ class PythonClientAPI int maxResults; int maxScanRows; int maxLockTime; + int maxOpenFiles; + int maxMemory; }; #endif diff --git a/PythonClientUser.h b/PythonClientUser.h index 14572c7..5514d7b 100644 --- a/PythonClientUser.h +++ b/PythonClientUser.h @@ -26,7 +26,7 @@ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - * $Id: //depot/main/p4-python/PythonClientUser.h#19 $ + * $Id: //depot/main/p4-python/PythonClientUser.h#20 $ * * Build instructions: * Use Distutils - see accompanying setup.py @@ -64,6 +64,7 @@ class PythonClientUser: public ClientUser, public KeepAlive virtual ClientProgress *CreateProgress(int); virtual int ProgressIndicator(); + virtual int CanParallelProgress() override { return 1; } virtual void Finished(); diff --git a/RELNOTES.txt b/RELNOTES.txt index 80e6e34..0b8613b 100644 --- a/RELNOTES.txt +++ b/RELNOTES.txt @@ -1,7 +1,7 @@ Release Notes for - P4Python, Helix Core API for Python + P4Python, P4 API for Python - Version 2024.1 + Version 2025.2 Introduction @@ -23,6 +23,17 @@ Introduction -------------------------------------------------------------------------- +Important Product Rebrand Notice + + Helix Core is now P4 + Perforce has a new look and logo that reflects our place in DevOps + workflows. As part of these changes, Helix Core is now P4. Name updates + and new icons to align with the P4 branding will be rolled out soon. + To learn more, see: + https://www.perforce.com/blog/vcs/introducing-the-p4-platform + +-------------------------------------------------------------------------- + Installation Installation via pip @@ -39,9 +50,10 @@ Installation python3 -m pip install --upgrade p4python - P4Python is distributed as a binary wheels for Python 3.8, 3.9, 3.10, - 3.11 and 3.12. In order for the binary wheel to be used, the ABI tag needs to - match.This often requires updated pip, to do so issue: + P4Python is distributed as a binary wheels for Python 3.10, + 3.11, 3.12, 3.13 and 3.14. In order for the binary wheel to be used, the ABI + tag needs to match. This often requires updated pip, to do so issue: + python3 -m pip install --upgrade pip Installation using Linux binary packages @@ -49,22 +61,40 @@ Installation P4Python can be installed via Linux packages available from: https://www.perforce.com/perforce-packages - Following packages are available: + The easiest way to install P4Python is using the meta-package: + + For RHEL/Rocky Linux + sudo yum install perforce-p4python3 - RHEL/Rocky Linux 8 + For Ubuntu/Debian + sudo apt-get install perforce-p4python3 + + The perforce-p4python3 meta-package automatically installs the correct + version for your system's default Python 3 installation. + + If you need to install P4Python for a specific Python version, + the following packages are available: + + RHEL/Rocky Linux 8 perforce-p4python3-python3.8 perforce-p4python3-python3.9 - + RHEL/Rocky Linux 9 perforce-p4python3-python3.9 - Ubuntu 20.04 + RHEL/Rocky Linux 10 + perforce-p4python3-python3.12 + + Ubuntu 20.04 perforce-p4python3-python3.8 perforce-p4python3-python3.9 - Ubuntu 22.04 + Ubuntu 22.04 perforce-p4python3-python3.10 + Ubuntu 24.04 + perforce-p4python3-python3.12 + Installing P4Python for Maya. Maya has a private python executable called "mayapy". @@ -101,52 +131,56 @@ Compatibility Statements Server Compatibility - You can use any release of P4Python with any release of the - Perforce server later than 2001.1 + This release of P4Python supports the 2025.2 P4 Server. + Older releases might work but are not supported. API Compatibility - This release of P4Python requires at least 2024.1 Perforce API - (2024.1/2596294). Older releases will not compile and are not supported. + This release of P4Python requires at least 2025.2 P4 C/C++ API + (2025.2/2852709). Older releases will not compile and are not supported. Python Compatibility This release of P4Python is supported building from source with - Python 3.8, 3.9, 3.10, 3.11 and 3.12. + Python 3.10, 3.11, 3.12, 3.13 and 3.14. For detailed compatibility, please check the following table: Python Release | P4Python Release ====================================== - 3.8 | 2020.1 or later - 3.9 | 2021.1 or later 3.10 | 2022.1 or later 3.11 | 2022.2 or later 3.12 | 2023.1 or later + 3.13 | 2024.2 or later + 3.14 | 2025.2 or later OpenSSL Compatibility To build P4Python with encrypted communication support, you must use the - version of OpenSSL that Perforce C/C++ API has been built against. + version of OpenSSL that P4 C/C++ API has been built against. - Beginning with the 2017.1 release of the Helix C/C++ API, the dependency on + Beginning with the 2017.1 release of the P4 C/C++ API, the dependency on OpenSSL is now enforced and the SSL stub library has been removed. - Executables linked against the P4API libraries must also be linked against - real OpenSSL libraries: The latest 3.0.x or 1.1.1 patch is recommended + Executables linked against the P4 C/C++ API libraries must also be linked against + real OpenSSL libraries: The latest 3.x.x or 1.1.1 patch is recommended Platform Compatibility This release is certified on the following platforms: - Windows (x86, x86_64) - Server 2016 + Windows for Intel(x86, x86_64) + Server 2016, 2019, 2022 + Windows 10, 11 Mac OS 11, 12 (x86_64) 12, 13 (ARM64) - Linux (x86, x86_64) - Ubuntu 20.04, 22.04 + Linux kernel 2.6+ for Intel(x86, x86_64) + Ubuntu 20.04, 22.04, 24.04 CentOS 8 - Rocky Linux 9.1 + Rocky Linux 9.1, 10 + Linux kernel 2.6+ for ARM(aarch64) + Ubuntu 20.04, 22.04, 24.04 + Rocky Linux 9.4, 10 The above platforms are tested and subject to regression testing on a frequent basis. Errors or bugs discovered in these platforms are prioritized @@ -156,33 +190,29 @@ Compatibility Statements Compiler Compatibility To build P4Python from source, you must use a version of P4Python that has - been compiled with the same compiler used to build the Perforce C++ API. + been compiled with the same compiler used to build the P4 C/C++ API. For most platforms, use gcc/g++. - On Linux since the 2019.1 P4API release, due to a change in library + On Linux since the 2019.1 P4 C/C++ API release, due to a change in library dependencies, the source code requires gcc 6 or above to be used. Attempting to use a different compiler or a different version of the compiler will cause link errors due to differences in name handling. - For more information about P4API compiler requirements, please see this link: + For more information about P4 C/C++ API compiler requirements, please see this link: https://www.perforce.com/manuals/p4api/Content/P4API/client.programming.compiling.html - On Windows platforms, the P4Python installer is build with Python from - python.org. The installer for Python 3.x was built with Visual Studio 2017 - and Visual Studio 2008 for Python 2. - To run Python 3.x on Windows, it might be necessary to install the redistributable version of the 2017 libraries, vc_redist.x64.exe or vc_redist.x86.exe, respectively for 64 bit and 32 bit. - Without these libraries the DLL for P4API will not load into Python and + Without these libraries the DLL for P4 C/C++ API will not load into Python and the command 'import P4' will fail. Known Limitations - The Perforce client-server protocol is not designed to support + The P4 client-server protocol is not designed to support multiple concurrent queries over the same connection. For this - reason, multi-threaded applications using the C++ API or the + reason, multi-threaded applications using the P4 C/C++ API or the script APIs (P4Perl, P4Ruby, etc.) should ensure that a separate connection is used for each thread or that only one thread may use a shared connection at a time. @@ -196,7 +226,7 @@ Compatibility Statements Attributes - Perforce P4Python provides the attributes listed below. Attributes + P4Python provides the attributes listed below. Attributes can be set in the P4() constructor or by using their setters and getters. For example: @@ -261,7 +291,7 @@ Attributes Tagged mode and form parsing - In Perforce P4Python 2007.3 and later, form parsing and tagged + In P4Python 2007.3 and later, form parsing and tagged output are enabled by default. (In Public Depot P4Python, tagged output and form parsing mode were disabled by default, but most scripts enabled them immediately.) @@ -281,8 +311,102 @@ Tagged mode and form parsing Key to symbols used in change notes below. * -- requires new P4Python - ** -- requires new p4d server program - *** -- requires new P4API + ** -- requires new P4 server program + *** -- requires new P4 C/C++ API + +-------------------------------------------------------------------------- + +New functionalities in 2025.2 (2025.2/2863679) (2025/12/03) + + #2842576 (Job #128780) + Built P4Python with P4API 2025.2 (2025.2/2852709) + + #2846296 (Job #128672) + Added support for python version 3.14 + + #2836086 (Job #110913) + Added support for per-file CreateProgress in the Python API + by implementing CanParallelProgress() in PythonClientUser + enabling parallel progress operations. + + #2832930 (Job #127506) + Added support for RPM packages on RHEL 10, Rocky Linux 10 + and other EL10-compatible distributions. + +Bugs fixed in 2025.2 + + #2837109 (Job #124136) + run_merge() now correctly reflects the merge tool’s exit code + instead of always returning True. + + #2850324 (Job #129466) + Fixed an issue where P4Python held the Python GIL during blocking + network operations in connect() and disconnect(), preventing other + threads and async workflows from running. + +-------------------------------------------------------------------------- + +New functionalities in 2025.1 (2025.1/2767466) (2025/05/21) + + #2757777 (Job #125785) + Built P4Python with P4 C/C++ API 2025.1 (2025.1/2761706) + +Bugs fixed in 2025.1 + + #2744582 (Job #123978) + Eliminated hardcoded bash paths, improving portability and flexibility. + Replaced shell=True with safer subprocess calls using command lists. + + #2743827 (Job #124153) + Updated P4Python build behavior to disable silent OPENSSL installation. + Users must now explicitly set the env variable "P4PYTHON_BUILD_SSL=yes" + to enable OPENSSL support during build. + If enabled, OPENSSL sources will be downloaded from the HTTPS-based + URL: https://www.openssl.org/source. + Added the support to build P4Python with OPENSSL 3.x. + + #2705918 (Job #124154) + Change auto_ptr into unique_ptr as auto_ptr is obsoleted from C++11. + + #2743928 (Job #124332) + Fixed exponential rise of the time needed to run p4.run_print() with + growing file size. + +-------------------------------------------------------------------------- + +New functionalities in 2024.2 (2024.2/2682690) (2024/11/15) + + #2677188 (Job #122598) + Built P4Python with P4API 2024.2 (2024.2/2675662) + + #2670765 (Job #122603) + Added wheels & build support for Python 3.13 + + #2659612 (Job #122764) + Added Linux binary package for Ubuntu24 + +Bugs fixed in 2024.2 (2024.2/2682690) (2024/11/15) + + #2669917 (Job #123092) + Fixed exception on joining non-string element in run args, + when logger is enabled. + +-------------------------------------------------------------------------- + +New functionalities in 2024.1 Patch 1 (2024.1/2645203) (2024/08/27) + + #2643104 (Job #119627) + Added wheels & build support for Linux ARM64/AARCH64 architecture. + +Bugs fixed in 2024.1 Patch 1 (2024.1/2645203) (2024/08/27) + + #2630486 (Job #121699) + Fixed error in P4.P4Exception class when creating object of class + with single string parameter. + + #2630485 (Job #121721) + Implemented p4.maxopenfiles and p4.maxmemory group specs as P4 Class + instance attributes. -------------------------------------------------------------------------- diff --git a/SpecMgr.cpp b/SpecMgr.cpp index 53f3be9..19dcf7f 100644 --- a/SpecMgr.cpp +++ b/SpecMgr.cpp @@ -23,7 +23,7 @@ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - $Id: //depot/main/p4-python/SpecMgr.cpp#57 $ + $Id: //depot/main/p4-python/SpecMgr.cpp#60 $ *******************************************************************************/ /******************************************************************************* @@ -116,6 +116,7 @@ struct specdata { "Backup;code:319;type:select;len:10;val:enable/disable;;" "View;code:311;fmt:C;type:wlist;words:2;len:64;;" "ChangeView;code:317;type:llist;len:64;;" + "LimitView;code:320;fmt:C;type:llist;len:64;;" }, { "depot", @@ -140,6 +141,7 @@ struct specdata { "MaxOpenFiles;code:413;type:word;len:12;;" "MaxMemory;code:NNN;type:word;len:12;;" "Timeout;code:406;type:word;len:12;;" + "IdleTimeout;code:NNN;type:word;len:12;;" "PasswordTimeout;code:409;type:word;len:12;;" "LdapConfig;code:410;type:line;len:128;;" "LdapSearchQuery;code:411;type:line;len:128;;" @@ -149,6 +151,10 @@ struct specdata { "Owners;code:408;type:wlist;len:32;opt:default;;" "Users;code:405;type:wlist;len:32;opt:default;;" }, + { + "hotfiles", + "HotFiles;code:1051;fmt:C;type:wlist;words:1;maxwords:3;len:64;opt:default;z;;" + }, { "job", "Job;code:101;rq;len:32;;" @@ -236,6 +242,7 @@ struct specdata { "LastPush;code:859;fmt:L;len:10;;" "DepotMap;code:860;type:wlist;words:2;len:64;;" "ArchiveLimits;code:862;type:wlist;words:2;len:64;;" + "RemoteCharset;code:863;opt:optional;type:line;len:32;;" }, { "repo", diff --git a/Version b/Version index 5ee71fd..ad40732 100644 --- a/Version +++ b/Version @@ -11,7 +11,7 @@ # SUPPDATE = 2010 06 17 ; # Of the build. The copyright date is derived from SUPPDATE. -RELEASE = 2024 1; -PATCHLEVEL = 2603570 ; -SUPPDATE = 2024 05 29 ; +RELEASE = 2025 1; +PATCHLEVEL = 2756918 ; +SUPPDATE = 2025 02 05 ; diff --git a/build.md b/build.md index 4683b96..3361a36 100644 --- a/build.md +++ b/build.md @@ -34,21 +34,37 @@ export apidir= export ssl= ``` + + 5. **[Optional] Control SSL Build Behavior** + + By default, if SSL is not available in the root environment, P4Python will attempt to build SSL silently during installation. + Since `pip install` is non-interactive, users cannot choose whether to build SSL or not during the process. + + To explicitly control this behavior, you can use the `P4PYTHON_BUILD_SSL` environment variable as a prefix to the install command: + + - If set to `"no"`, the build process will skip building SSL. + - If set to `"yes"`, the build process will proceed to build SSL if needed. + - If not set, the default is `"no"` (SSL will not be built). + + **Example usage:** + ``` + P4PYTHON_BUILD_SSL=yes python3 -m pip install . + ``` - 5. To install P4Python, execute the following command from P4Python source code directory: + 6. To install P4Python, execute the following command from P4Python source code directory: ``` python3 -m pip install . ``` **Note:** In order to cleanly reinstall P4Python, remove the directory named "build". - 6. To test your P4Python install, execute p4test.py: + 7. To test your P4Python install, execute p4test.py: ``` python3 p4test.py ``` **Note:** This test requires the Perforce server executable p4d 17.1 or better to be installed and in the PATH. - 7. To build P4Python wheel, execute the following command: + 8. To build P4Python wheel, execute the following command: ``` python3 -m pip wheel . -w dist diff --git a/linux_build/build-wheels.sh b/linux_build/build-wheels.sh index eb8586d..fc83cf1 100755 --- a/linux_build/build-wheels.sh +++ b/linux_build/build-wheels.sh @@ -1,11 +1,18 @@ -#!/bin/bash +#!/usr/bin/env bash set -eEx +ARCH=$(arch) -export PATH=$PATH:/work/p4-bin/bin.linux26x86_64/ +export PATH=$PATH:/work/p4-bin/bin.linux26${ARCH}/ # Extract the p4api and set the P4API path var mkdir -p /work/p4-api -tar xvfz /work/p4-bin/bin.linux26x86_64/p4api-glibc2.12-openssl3.tgz -C /work/p4-api +# Check the condition +if [ "$ARCH" = x86_64 ]; then + tar xvfz /work/p4-bin/bin.linux26${ARCH}/p4api-glibc2.12-openssl3.tgz -C /work/p4-api +else + tar xvfz /work/p4-bin/bin.linux26${ARCH}/p4api-openssl3.tgz -C /work/p4-api +fi + P4API=`echo /work/p4-api/p4api-20*` cd /work/p4-python @@ -17,7 +24,7 @@ mkdir -p repair # Compile wheels for VERSION in $1; do - PYBIN="/opt/python/$(ls /opt/python/ | grep cp$VERSION | grep -v 27mu)/bin" + PYBIN="/opt/python/$(ls /opt/python/ | grep -E "cp$VERSION-cp$VERSION$")/bin" ## Set env path for apidir and ssl directories export apidir=$P4API @@ -38,14 +45,14 @@ for VERSION in $1; do ## Test the build "${PYBIN}/python" p4test.py - ## Repair wheel with new ABI tag manylinux_2_28_x86_64 - #auditwheel repair dist/p4python-*-linux_x86_64.whl --plat manylinux_2_28_x86_64 -w repair + ## Repair wheel with new ABI tag manylinux_2_28_${ARCH} + #auditwheel repair dist/p4python-*-linux_${ARCH}.whl --plat manylinux_2_28_${ARCH} -w repair - #Changing wheel platform ABI tag to manylinux_2_28_x86_64 - "${PYBIN}/python" -m wheel tags --platform-tag=manylinux_2_28_x86_64 dist/p4python-*-linux_x86_64.whl + #Changing wheel platform ABI tag to manylinux_2_28_${ARCH} + "${PYBIN}/python" -m wheel tags --platform-tag=manylinux_2_28_${ARCH} dist/p4python-*-linux_${ARCH}.whl #Move wheel to repair directory - mv dist/p4python-*-manylinux_2_28_x86_64.whl repair + mv dist/p4python-*-manylinux_2_28_${ARCH}.whl repair rm -rf build p4python.egg-info dist done diff --git a/p4test.py b/p4test.py index df80962..1716e5f 100644 --- a/p4test.py +++ b/p4test.py @@ -112,6 +112,8 @@ def testEnvironment(self): self.p4.maxresults = 100000 self.p4.maxscanrows = 1000000 self.p4.maxlocktime = 10000 + self.p4.maxopenfiles = 1000 + self.p4.maxmemory = 2000 self.p4.password = "mypassword" self.p4.port = "myserver:1666" self.p4.prog = "myprogram" @@ -126,6 +128,8 @@ def testEnvironment(self): self.assertEqual( self.p4.maxresults, 100000, "maxresults" ) self.assertEqual( self.p4.maxscanrows, 1000000, "maxscanrows" ) self.assertEqual( self.p4.maxlocktime, 10000, "maxlocktime" ) + self.assertEqual( self.p4.maxopenfiles, 1000, "maxopenfiles" ) + self.assertEqual( self.p4.maxmemory, 2000, "maxmemory" ) self.assertEqual( self.p4.password, "mypassword", "password" ) self.assertEqual( self.p4.port, "myserver:1666", "port" ) self.assertEqual( self.p4.tagged, 1, "tagged" ) @@ -504,7 +508,7 @@ def resolve(self, mergeData): return "at" def actionResolve(self, mergeData): - self.t.assertEqual(mergeData.merge_action, "(text+wx)", + self.t.assertEqual(mergeData.merge_action, "(text+Dwx)", "Unexpected mergeAction: '%s'" % mergeData.merge_action ) self.t.assertEqual(mergeData.yours_action, "(text+w)", "Unexpected mergeAction: '%s'" % mergeData.yours_action ) @@ -680,7 +684,6 @@ def testOutputHandler( self ): # test the resetting self.p4.handler = None self.assertEqual( self.p4.handler, None ) - self.assertEqual( sys.getrefcount(h), 2 ) self.p4.connect() self._setClient() @@ -713,14 +716,12 @@ def outputMessage(self, msg): self._doSubmit("Failed to submit the add", change) h = MyOutputHandler() - self.assertEqual( sys.getrefcount(h), 2 ) self.p4.handler = h self.assertEqual( len(self.p4.run_files('...')), 0, "p4 does not return empty list") self.assertEqual( len(h.statOutput), len(files), "Less files than expected") self.assertEqual( len(h.messageOutput), 0, "Messages unexpected") self.p4.handler = None - self.assertEqual( sys.getrefcount(h), 2 ) def testProgress( self ): self.p4.connect() @@ -1457,6 +1458,90 @@ def isAlive(self): self.assertEqual(100, total_files, "Setbreak is not working") self.p4.disconnect() + def testMergeToolReturnCode(self): + testDir = 'test_merge_return' + testAbsoluteDir = os.path.join(self.client_root, testDir) + os.mkdir(testAbsoluteDir) + + self.p4.connect() + self.assertTrue(self.p4.connected(), "Not connected") + self._setClient() + + # Create initial file + file = "merge_test.txt" + fname = os.path.join(testAbsoluteDir, file) + with open(fname, "w") as f: + f.write("Line 1\n") + textFile = testDir + "/" + file + self.p4.run_add(textFile) + + change = self.p4.fetch_change() + change._description = "Initial version" + self._doSubmit("Failed to submit initial", change) + + # Create second revision + self.p4.run_edit(textFile) + with open(fname, "w") as f: + f.write("Line 1\nLine 2\n") + + change = self.p4.fetch_change() + change._description = "Second version" + self._doSubmit("Failed to submit second", change) + + # Sync back and create conflict + self.p4.run_sync(textFile + "#1") + self.p4.run_edit(textFile) + with open(fname, "w") as f: + f.write("Line 1\nDifferent Line 2\n") + self.p4.run_sync(textFile) + + # Test with failing merge tool + import tempfile + import stat + + # Create a fake merge tool that always fails + failing_merge_script = os.path.join(tempfile.gettempdir(), "failing_merge.sh") + with open(failing_merge_script, "w") as f: + f.write("#!/bin/bash\n >&2\nexit 1\n") + os.chmod(failing_merge_script, stat.S_IRWXU) + + class MergeTestResolver(P4.Resolver): + def __init__(self, testObject): + self.t = testObject + self.merge_result = None + + def resolve(self, mergeData): + # Set P4MERGE to our failing script + old_p4merge = os.environ.get('P4MERGE', '') + os.environ['P4MERGE'] = failing_merge_script + + try: + # Call run_merge and verify it returns False when merge tool fails + self.merge_result = mergeData.run_merge() + + # Assert that run_merge() correctly detects merge tool failure + self.t.assertFalse(self.merge_result, + f"run_merge() should return False when merge tool fails with exit code 1, but returned {self.merge_result}") + + # Additional assertion to verify the type + self.t.assertIsInstance(self.merge_result, bool, + f"run_merge() should return a boolean, but returned {type(self.merge_result)}") + + finally: + # Restore original P4MERGE + if old_p4merge: + os.environ['P4MERGE'] = old_p4merge + elif 'P4MERGE' in os.environ: + del os.environ['P4MERGE'] + + return "at" # Accept theirs to complete the resolve + + resolver = MergeTestResolver(self) + self.p4.run_resolve(resolver=resolver) + + # Clean up + if os.path.exists(failing_merge_script): + os.unlink(failing_merge_script) if __name__ == '__main__': diff --git a/setup.py b/setup.py index f642ecc..d2a8b8e 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,6 @@ import os, os.path, sys, re, shutil, stat, subprocess -from tools.P4APIFtp import P4APIFtp from tools.PlatformInfo import PlatformInfo from tools.VersionInfo import VersionInfo from tools.P4APIHttps import P4APIHttps @@ -167,7 +166,7 @@ def download_p4api(self, api_ver, ssl_ver): print("Looking for P4 API {0} for SSL {1} on https://ftp.perforce.com".format(api_ver, ssl_ver)) - g_major, g_minor = P4APIFtp.get_glib_ver(self) + g_major, g_minor = P4APIHttps.get_glib_ver(self) p4https = P4APIHttps() url = p4https.get_url(g_minor, ssl_ver, api_ver) api_dir, api_tarball= p4https.get_file(url) @@ -190,23 +189,36 @@ def get_ssl_version_from_p4api(self): @staticmethod def build_ssl_lib(ssl_ver): - # not for windows # get the openssl version needed # Now that p4api needs to know the ssl version, this call gets recursive. # ssl_ver = self.get_ssl_version_from_p4api(self) + + # Default OpenSSL version if none is provided if not ssl_ver: - ssl_ver = "1.1.1" # default to latest ssl version + ssl_ver = "3.0.2" # Default to the latest supported SSL version + + # Check environment variable for build_ssl + # If set to "no", skip the build process + # If set to "yes", proceed with the build process + # If not set, default to "no" + build_ssl = os.getenv("P4PYTHON_BUILD_SSL", "no").lower() + if build_ssl != "yes": + print("Skipping OpenSSL build as per environment variable.") + return "", "", "", False + + # Proceed to download and build OpenSSL + print(f"Downloading OpenSSL {ssl_ver} source from https://www.openssl.org/source/") + ssl_src_dir, tarball = P4APIHttps().get_ssl_src(ssl_ver) - # try to download the SSL source from the ftp site - print("Downloading SSL {0} source from ftp.openssl.org".format(ssl_ver)) - (ssl_src_dir, tarball) = P4APIFtp("ssl").get_ssl_src(ssl_ver) + if not ssl_src_dir: + print("Failed to download or extract OpenSSL source.") + return "", "", "", False - print("Building SSL source in {0}".format(ssl_src_dir)) - ssl_from_ftp = True - ssl_lib_dir = P4APIFtp("ssl").build_ssl(ssl_src_dir) + print(f"Building OpenSSL source in {ssl_src_dir}") + ssl_lib_dir = P4APIHttps().build_ssl(ssl_src_dir) - return ssl_lib_dir, ssl_src_dir, tarball, ssl_from_ftp + return ssl_lib_dir, ssl_src_dir, tarball, True @staticmethod def check_installed_ssl(): @@ -244,17 +256,33 @@ def check_installed_ssl(): for p in os.environ["PATH"].split(os.pathsep): pathToFile = os.path.join(p, "openssl") if os.path.exists(pathToFile) and os.access(pathToFile, os.X_OK): - entry = subprocess.check_output("ldd {0} | grep libssl".format(pathToFile), - executable="/bin/bash", shell="True") - if entry is not False: - libpath = os.path.dirname(entry.split()[2]) - - if os.path.exists(libpath) and os.path.isdir(libpath): - print("Found installed SSL libraries " + libpath.decode('utf-8')) - return libpath, ver_only - else: + try: + output = subprocess.check_output(["ldd", pathToFile], text=True) + for line in output.splitlines(): + if "libssl" in line: + parts = line.split() + if len(parts) >= 3: + libpath = os.path.dirname(parts[2]) + break + else: + raise Exception(f"libssl not found in ldd output for {pathToFile}") + + if os.path.exists(libpath) and os.path.isdir(libpath): + print("Found installed SSL libraries " + libpath) + return libpath, ver_only + else: + print("****************************************************", file=sys.stderr) + print(f"Calculated path {libpath} for SSL does not exist", file=sys.stderr) + print("****************************************************", file=sys.stderr) + return "", "" + except subprocess.CalledProcessError as e: + print("****************************************************", file=sys.stderr) + print(f"Error running ldd on {pathToFile}: {e}", file=sys.stderr) + print("****************************************************", file=sys.stderr) + return "", "" + except Exception as e: print("****************************************************", file=sys.stderr) - print("Calculated path {0} for SSL does not exist".format(libpath), file=sys.stderr) + print(f"Error processing ldd output: {e}", file=sys.stderr) print("****************************************************", file=sys.stderr) return "", "" else: @@ -281,14 +309,17 @@ def run(self, *args, **kwargs): # check for a version of SSL already installed via 'openssl version' self.ssl, ssl_ver = self.check_installed_ssl() # return libpath or None - # we only support 1.0.2 or 1.1.1 using 2019.1 p4api - if not (("1.0.2" in ssl_ver) or ("1.1.1" in ssl_ver) or ("3.0" in ssl_ver)): + # we only support 1.1.1 or 3 using 2019.1 p4api + if not(("1.1.1" in ssl_ver) or (ssl_ver.startswith("3"))): self.ssl = "" if not self.ssl: # try downloading and building ssl if self.is_super(): (self.ssl, ssl_src, ssl_tarball, loaded_ssl_from_ftp) = self.build_ssl_lib(ssl_ver) + if not self.ssl: + print("OpenSSL build was skipped or failed.") + raise Exception("Cannot build P4Python without SSL support.") p4_ssl_dir = self.ssl p4_ssl_ver = ssl_ver else: diff --git a/tools/P4APIFtp.py b/tools/P4APIFtp.py deleted file mode 100644 index 0a06bfb..0000000 --- a/tools/P4APIFtp.py +++ /dev/null @@ -1,284 +0,0 @@ -from __future__ import print_function -import platform - -import tarfile -import tempfile -import re -import os, os.path -from ftplib import FTP, error_perm -import itertools -from .Version import Version -import subprocess - -PERFORCE_FTP = 'ftp.perforce.com' -OPENSSL_FTP = 'ftp.openssl.org' - -# user is anonymous, no need to log on with a special user - - -class P4APIFtp: - def __init__(self, site): - if site == "perforce": - self.ftp = FTP(PERFORCE_FTP) - else: - self.ftp = FTP(OPENSSL_FTP) - - self.pattern = re.compile(r'(?P[ld-][r-][w-][x-][r-][w-][x-][r-][w-][x-])' + \ - r'\s+\d+\s+\w+\s+\w+\s+\d+\s+' + \ - r'(?P\S+\s+\S+\s+\S+)\s+' + \ - r'(?P.+)') - self.platform = self.findPlatform - - @property - def findPlatform(self): - """ - We are looking for out platform following the Perforce naming standard, - i.e. bin.xxxyyy - :return: the platform we are on - """ - - architecture = platform.architecture()[0] # 32bit or 64bit - machine = platform.machine() - system = platform.system() - uname = platform.uname() - - platform_str = "bin." - - if system == "Linux": - platform_str += "linux26" - if machine in ['i386', 'i586', 'i686', 'x86']: - platform_str += "x86" - elif machine in ['x86_64', 'amd64']: - platform_str += "x86_64" - elif machine in ['armv6l']: - platform_str += 'armhf' - else: - raise Exception("Unknown machine {}. Please configure P4API manually".format(machine)) - - elif system == "Windows": - platform_str = platform_str + "NT" - if architecture == "32bit": - platform_str += "X86" - else: - platform_str += "X64" - - elif system == "Darwin": - platform_str = platform_str + "darwin90" + machine - - elif system == "FreeBSD": - platform_str += "freebsd" - release = uname.release - - value = int(''.join(itertools.takewhile(lambda s: s.isdigit(), release))) - - if value >= 10: - platform_str += "100" - if machine == 'amd64': - platform_str += "x86_64" - elif machine == 'i386': - platform_str += "x86" - else: - raise Exception("Unknown machine {}. Please configure P4API manually".format(machine)) - - - else: - raise Exception( - "System {} is not supported for automatic download. Please configure P4API manually".format(system)) - - return platform_str - - def get_glib_ver(self): - pattern = re.compile(r"ldd\s+\(.*\)\s+(\d+)\.(\d+)") - gentry = subprocess.check_output("ldd --version | grep ldd", executable="/bin/bash", shell="True") - if type(gentry) == bytes: - gentry = gentry.decode() - match = pattern.match(gentry) - if match: - return match.group(1),match.group(2) - else: - raise Exception("Cannot parse {}".format(gentry)) - - - def get_ssl_ver(self, ssl_ver_string): - pattern = re.compile(r"(\d+)\.(\d+).(\d+).*") - match = pattern.match(str(ssl_ver_string)) - if match: - return match.group(1), match.group(2), match.group(3) - return "1", "1", "1" - - - def sorted_directories(self, dirs): - # first, extract the directory names from the list output - names = [self.pattern.match(x).group('name') for x in dirs] - - # sort them by date, avoiding the Y2K problem - sorted_names = sorted(names, key=lambda x: '19' + x if x[1] == '9' else '20' + x, reverse=True) - - return sorted_names - - def descend(self, d, f): - pwd = self.ftp.pwd() - apidir = None - tar = None - - try: - self.ftp.cwd(d) - self.ftp.cwd(self.platform.lower()) - p4api = 'p4api.tgz' - - files = [] - self.ftp.retrlines("LIST", lambda s: files.append(s)) - - for file in files: - if f in file: - p4api = f - break - - tempdir = tempfile.gettempdir() - filename = os.path.join(tempdir, p4api) - - # if the api tarball exists, don't re-download it. - if not os.path.exists(filename): - with open(filename, 'wb') as f: - self.ftp.retrbinary('RETR ' + p4api, f.write) - - tar = tarfile.open(filename, 'r') - apidir = os.path.join(tempdir, tar.getnames()[0]) - - # if apidir exists, don't unpack again, otherwise read-only errors will occur - if not (os.path.exists(apidir) and os.path.isdir(apidir)): - tar.extractall(tempdir) - except error_perm as e: - return None - finally: - self.ftp.cwd(pwd) - - if tar: - tar.close() - - return apidir, p4api - - def findAPI(self, names, api_ver, ssl_ver): - """ - Searches through the provided list of directories depth first. - :param names: list of directories - :param api_ver: p4 API version - :param ssl_ver: ssl version - :return: The path to the API dir or None - """ - - # compute a list of preferred names - g_major, g_minor = self.get_glib_ver() - - if int(g_minor) <= 3: - glib_ver = "2.3" - else: - glib_ver = "2.12" - - s_major, s_minor, s_mini = self.get_ssl_ver(ssl_ver) - - if int(s_minor) == 0: - ssl_ver = "1.0.2" - else: - ssl_ver = "1.1.1" - - f = "p4api-glibc{0}-openssl{1}.tgz".format(glib_ver, ssl_ver) - - # start with the first one - # drill down in the directory - # find the correct platform - # descend into the platform directory - # see if we can find p4api.tgz - # otherwise, use next dated directory and start over - - for d in names: - try: - dir_version = Version(d) - if dir_version > api_ver: - continue - api_dir, api_tar = self.descend(d, f) - if api_dir: - return api_dir, api_tar - except: - pass - - return "", f - - - def load_api(self, api_ver, ssl_ver): - self.ftp.connect() - self.ftp.login() - self.ftp.cwd("perforce") - - dirs = [] - # self.ftp.set_debuglevel(2) - self.ftp.retrlines("LIST", lambda str1: dirs.append(str1)) - - s = self.sorted_directories(dirs) - return self.findAPI(s, api_ver, ssl_ver) - - - def build_ssl(self, src_dir): - print("building openssl in {0}".format(src_dir)) - save_dir = os.getcwd() - os.chdir(src_dir) - rv = subprocess.call(['./config']) - if rv == 0: - rv = subprocess.call(['make']) - if rv == 0: - rv = subprocess.call(['make', 'install']) - if rv == 0: - os.chdir(save_dir) - return("/usr/local/ssl/lib") - os.chdir(save_dir) - return "" - - def grab_ssl_src_from_ftp(self, name): - pwd = self.ftp.pwd() - tar = None - - try: - tempdir = tempfile.gettempdir() - filename = os.path.join(tempdir, name) - - # if the tarball already exists, don't download it again - if not os.path.exists(filename): - with open(filename, 'wb') as f: - self.ftp.retrbinary('RETR ' + name, f.write) - - ssl_src_dir = filename[0:-7] # remove trailing .tar.gz from filename - tar = tarfile.open(filename, 'r') - - # if ssldir exists, don't unpack again, otherwise read-only errors will occur - if not (os.path.exists(ssl_src_dir) and os.path.isdir(ssl_src_dir)): - tar.extractall(tempdir) - - except error_perm as e: - return "","" - finally: - self.ftp.cwd(pwd) - - if tar: - tar.close() - - return ssl_src_dir, filename - - # load the code from openSSL - def get_ssl_src(self, ssl_ver): - self.ftp.connect() - self.ftp.login() - self.ftp.cwd("source") - print("looking for " + 'openssl-' + ssl_ver) - dirs = [] - self.ftp.retrlines("LIST", lambda str: dirs.append(str.split()[8])) - match = "" - for i, entry in enumerate(dirs): - if entry.startswith("openssl-" + ssl_ver) and entry.endswith('.tar.gz'): - match = entry - break - - if match: - return self.grab_ssl_src_from_ftp(match) - - print("unable to find openssl-" + ssl_ver) - return "", "" diff --git a/tools/P4APIHttps.py b/tools/P4APIHttps.py index 355368f..1d2a095 100644 --- a/tools/P4APIHttps.py +++ b/tools/P4APIHttps.py @@ -1,10 +1,12 @@ import os +import platform import shutil import urllib.request import urllib.error import re import tarfile import tempfile +import subprocess class P4APIHttps: @@ -14,7 +16,21 @@ def get_ssl_ver(self, ssl_ver_string): match = pattern.match(str(ssl_ver_string)) if match: return match.group(1), match.group(2), match.group(3) - return "1", "1", "1" + return "3", "0", "2" + + def get_glib_ver(self): + pattern = re.compile(r"ldd\s+\(.*\)\s+(\d+)\.(\d+)") + try: + gentry = subprocess.check_output(["ldd", "--version"], text=True) + match = pattern.search(gentry) + if match: + return match.group(1), match.group(2) + else: + raise Exception(f"Cannot parse ldd version from output: {gentry}") + except subprocess.CalledProcessError as e: + raise Exception(f"Failed to execute 'ldd --version': {e}") + except FileNotFoundError: + raise Exception("The 'ldd' command is not available on this system.") def get_url(self, g_minor, ssl_ver, api_ver): @@ -25,14 +41,17 @@ def get_url(self, g_minor, ssl_ver, api_ver): s_major, s_minor, s_mini = self.get_ssl_ver(ssl_ver) - # When server releases ssl 3.x.x version of p4api, this will need to accommodate it - if (int(s_major) == 1) and (int(s_minor) == 0): - ssl_ver = "1.0.2" - else: + if int(s_major) == 1: ssl_ver = "1.1.1" + elif int(s_major) == 3: + ssl_ver = "3" - f = "p4api-glibc{0}-openssl{1}.tgz".format(glib_ver, ssl_ver) - url = "https://ftp.perforce.com/perforce/{0}/bin.linux26x86_64/{1}".format(api_ver, f) + if platform.machine() in ["aarch64", "arm64"]: + f = "p4api-openssl{1}.tgz".format(glib_ver, ssl_ver) + url = "https://ftp.perforce.com/perforce/{0}/bin.linux26aarch64/{1}".format(api_ver, f) + else: + f = "p4api-glibc{0}-openssl{1}.tgz".format(glib_ver, ssl_ver) + url = "https://ftp.perforce.com/perforce/{0}/bin.linux26x86_64/{1}".format(api_ver, f) return url def get_file(self, url): @@ -70,3 +89,57 @@ def get_file(self, url): os.remove(p4api) if os.path.exists(apidir) and os.path.isdir(apidir): shutil.rmtree(apidir, ignore_errors=True) + + def build_ssl(self, src_dir): + print("building openssl in {0}".format(src_dir)) + save_dir = os.getcwd() + os.chdir(src_dir) + rv = subprocess.call(['./config']) + if rv == 0: + rv = subprocess.call(['make']) + if rv == 0: + rv = subprocess.call(['make', 'install']) + if rv == 0: + os.chdir(save_dir) + return("/usr/local/ssl/lib") + os.chdir(save_dir) + return "" + + def get_ssl_src(self, ssl_ver): + """ + Downloads and extracts the OpenSSL source code from the official OpenSSL website. + :param ssl_ver: The version of OpenSSL to download (e.g., "3.0.2"). + :return: The directory where the source is extracted, the tarball path. + """ + url = f"https://www.openssl.org/source/openssl-{ssl_ver}.tar.gz" + print(f"Downloading OpenSSL source from {url}") + + tempdir = tempfile.gettempdir() + tarball = os.path.join(tempdir, f"openssl-{ssl_ver}.tar.gz") + ssl_src_dir = os.path.join(tempdir, f"openssl-{ssl_ver}") + + try: + # Download the tarball if it doesn't already exist + if not os.path.exists(tarball): + with urllib.request.urlopen(url) as response, open(tarball, 'wb') as out_file: + out_file.write(response.read()) + + # Extract the tarball if the directory doesn't already exist + if not os.path.exists(ssl_src_dir): + with tarfile.open(tarball, 'r:gz') as tar: + tar.extractall(tempdir) + + print(f"OpenSSL source extracted to {ssl_src_dir}") + return ssl_src_dir, tarball + except (urllib.error.HTTPError, urllib.error.URLError) as e: + print(f"Failed to download OpenSSL source: {e}") + except tarfile.TarError as e: + print(f"Failed to extract OpenSSL source: {e}") + + # Cleanup in case of failure + if os.path.exists(tarball): + os.remove(tarball) + if os.path.exists(ssl_src_dir): + shutil.rmtree(ssl_src_dir, ignore_errors=True) + + return "", "" diff --git a/tools/PlatformInfo.py b/tools/PlatformInfo.py index 9ffc13e..d681ec8 100644 --- a/tools/PlatformInfo.py +++ b/tools/PlatformInfo.py @@ -108,7 +108,7 @@ def __init__(self, api_version, release_version, ssl, ssl_ver): if unameOut.machine == 'arm64': os.environ["MACOSX_DEPLOYMENT_TARGET"] = "12.6" else: - os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.12" + os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.15" arch = self.architecture(unameOut[4]) @@ -180,6 +180,8 @@ def architecture(self, str): return 'X86_64' elif str == 'sparc': return 'SPARC' + elif str == 'aarch64': + return 'ARM' elif re.match('arm.*', str): return "ARM" @@ -188,4 +190,4 @@ def get_ssl_ver(self, ssl_ver_string): match = pattern.match(ssl_ver_string) if match: return match.group(1), match.group(2), match.group(3) - return "1", "1", "1" + return "3", "0", "2"