@@ -10,12 +10,42 @@ class EmbeddedPython(ConanFile):
1010 description = "Embedded distribution of Python"
1111 url = "https://www.python.org/"
1212 license = "PSFL"
13- settings = {"os" : ["Windows" ]}
14- options = {"version" : "ANY" , "packages" : "ANY" }
15- default_options = "packages=None"
13+ settings = "os" , "compiler" , "build_type" , "arch"
14+ options = {
15+ "version" : "ANY" ,
16+ "packages" : "ANY" ,
17+ "openssl_variant" : ["lowercase" , "uppercase" ] # see explanation in `build_requirements()`
18+ }
19+ default_options = {
20+ "packages" : None ,
21+ "openssl_variant" : "lowercase"
22+ }
1623 exports = "embedded_python_tools.py" , "embedded_python.cmake"
1724 short_paths = True # some of the pip packages go over the 260 char path limit on Windows
1825
26+ def config_options (self ):
27+ """On Windows, we download a binary so these options have no effect"""
28+ if self .settings .os == "Windows" :
29+ del self .settings .compiler
30+ del self .settings .build_type
31+
32+ def build_requirements (self ):
33+ """On Windows, we download a binary so we don't need anything else"""
34+ if self .settings .os == "Windows" :
35+ return
36+
37+ self .build_requires ("sqlite3/3.35.5" )
38+ self .build_requires ("bzip2/1.0.8" )
39+
40+ # The pre-conan-center-index version of `openssl` was capitalized as `OpenSSL`.
41+ # Both versions can't live in the same Conan cache so we need this compatibility
42+ # option to pick the available version. The cache case-sensitivity issue should
43+ # be solved in Conan 2.0, but we need this for now.
44+ if self .options .openssl_variant == "lowercase" :
45+ self .build_requires ("openssl/1.1.1k" )
46+ else :
47+ self .build_requires ("OpenSSL/1.1.1f" )
48+
1949 @property
2050 def pyversion (self ):
2151 """Full Python version that we want to package"""
@@ -64,7 +94,10 @@ def _gather_licenses(self, bootstrap):
6494
6595 def build (self ):
6696 prefix = pathlib .Path (self .build_folder ) / "embedded_python"
67- build_helper = WindowsBuildHelper (self , prefix )
97+ if self .settings .os == "Windows" :
98+ build_helper = WindowsBuildHelper (self , prefix )
99+ else :
100+ build_helper = UnixLikeBuildHelper (self , prefix )
68101 build_helper .build_embedded ()
69102
70103 if not self .options .packages :
@@ -142,3 +175,72 @@ def build_bootstrap(self):
142175
143176 return python_exe
144177
178+
179+ class UnixLikeBuildHelper :
180+ def __init__ (self , conanfile , prefix_dir ):
181+ self .conanfile = conanfile
182+ self .prefix_dir = prefix_dir
183+
184+ def _get_source (self ):
185+ url = f"https://github.com/python/cpython/archive/v{ self .conanfile .pyversion } .tar.gz"
186+ tools .get (url )
187+ os .rename (f"cpython-{ self .conanfile .pyversion } " , "src" )
188+
189+ @property
190+ def _openssl_path (self ):
191+ if self .conanfile .options .openssl_variant == "lowercase" :
192+ pck = "openssl"
193+ else :
194+ pck = "OpenSSL"
195+ return self .conanfile .deps_cpp_info [pck ].rootpath
196+
197+ def _build (self , dest_dir ):
198+ from conans import AutoToolsBuildEnvironment
199+
200+ autotools = AutoToolsBuildEnvironment (self .conanfile )
201+ env_vars = autotools .vars
202+
203+ # On Linux, we need to set RPATH so that `root/bin/python3` can correctly find the `.so`
204+ # file in `root/lib` no matter where `root` is. We need it to be portable. We explicitly
205+ # set `--disable-new-dtags` to use RPATH instead of RUNPATH. RUNPATH can be overridden by
206+ # the LD_LIBRARY_PATH env variable which is not at all what we want for this self-contained
207+ # package. Unlike RUNPATH, RPATH takes precedence over LD_LIBRARY_PATH.
208+ if self .conanfile .settings .os == "Linux" :
209+ env_vars ["LDFLAGS" ] += " -Wl,-rpath,'$$ORIGIN/../lib' -Wl,--disable-new-dtags"
210+
211+ config_args = " " .join ([
212+ "--enable-shared" ,
213+ f"--prefix={ dest_dir } " ,
214+ f"--with-openssl={ self ._openssl_path } " ,
215+ ])
216+
217+ tools .mkdir ("./build" )
218+ with tools .chdir ("./build" ), tools .environment_append (env_vars ):
219+ self .conanfile .run (f"../src/configure { config_args } " )
220+ self .conanfile .run ("make -j8" )
221+ self .conanfile .run ("make install -j8" )
222+
223+ ver = "." .join (self .conanfile .pyversion .split ("." )[:2 ])
224+ exe = str (dest_dir / f"bin/python{ ver } " )
225+ self .conanfile .run (f"{ exe } -m pip install -U pip==21.1.1" )
226+
227+ # Move the license file to match the Windows layout
228+ lib_dir = dest_dir / "lib"
229+ os .rename (lib_dir / f"python{ ver } /LICENSE.txt" , dest_dir / "LICENSE.txt" )
230+
231+ # Give write permissions, otherwise end-user projects won't be able to re-import
232+ # the shared libraries (re-import happens on subsequent `conan install` runs).
233+ for file in lib_dir .glob ("libpython*" ):
234+ self .conanfile .run (f"chmod 777 { file } " )
235+
236+ def build_embedded (self ):
237+ self ._get_source ()
238+ self ._build (self .prefix_dir )
239+
240+ def enable_site_packages (self ):
241+ """These are enabled by default when building from source"""
242+ pass
243+
244+ def build_bootstrap (self ):
245+ """For now, as a shortcut, we'll let the Unix-like builds bootstrap themselves"""
246+ return self .prefix_dir / "bin/python3"
0 commit comments