conanfile.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. import os
  2. from io import StringIO
  3. from pathlib import Path
  4. from jinja2 import Template
  5. from conan import ConanFile
  6. from conan.tools.files import copy, rmdir, save, mkdir, rm, update_conandata
  7. from conan.tools.microsoft import unix_path
  8. from conan.tools.env import VirtualRunEnv, Environment, VirtualBuildEnv
  9. from conan.tools.scm import Version
  10. from conan.errors import ConanInvalidConfiguration, ConanException
  11. required_conan_version = ">=2.7.0"
  12. class CuraConan(ConanFile):
  13. name = "cura"
  14. license = "LGPL-3.0"
  15. author = "UltiMaker"
  16. url = "https://github.com/Ultimaker/cura"
  17. description = "3D printer / slicing GUI built on top of the Uranium framework"
  18. topics = ("conan", "python", "pyqt6", "qt", "qml", "3d-printing", "slicer")
  19. build_policy = "missing"
  20. exports = "LICENSE*", "*.jinja"
  21. settings = "os", "compiler", "build_type", "arch"
  22. generators = "VirtualPythonEnv"
  23. tool_requires = "gettext/0.22.5"
  24. # FIXME: Remove specific branch once merged to main
  25. python_requires = "translationextractor/[>=2.2.0]@ultimaker/cura_11622"
  26. options = {
  27. "enterprise": [True, False],
  28. "staging": [True, False],
  29. "cloud_api_version": ["ANY"],
  30. "display_name": ["ANY"], # TODO: should this be an option??
  31. "cura_debug_mode": [True, False], # FIXME: Use profiles
  32. "internal": [True, False],
  33. "i18n_extract": [True, False],
  34. }
  35. default_options = {
  36. "enterprise": False,
  37. "staging": False,
  38. "cloud_api_version": "1",
  39. "display_name": "UltiMaker Cura",
  40. "cura_debug_mode": False, # Not yet implemented
  41. "internal": False,
  42. "i18n_extract": False,
  43. }
  44. def set_version(self):
  45. if not self.version:
  46. self.version = self.conan_data["version"]
  47. @property
  48. def _app_name(self):
  49. if self.options.enterprise:
  50. return str(self.options.display_name) + " Enterprise"
  51. return str(self.options.display_name)
  52. @property
  53. def _urls(self):
  54. if self.options.staging:
  55. return "staging"
  56. return "default"
  57. @property
  58. def _root_dir(self):
  59. return Path(self.deploy_folder if hasattr(self, "deploy_folder") else self.source_folder)
  60. @property
  61. def _base_dir(self):
  62. return self._root_dir.joinpath("venv")
  63. @property
  64. def _share_dir(self):
  65. return self._base_dir.joinpath("share")
  66. @property
  67. def _script_dir(self):
  68. if self.settings.os == "Windows":
  69. return self._base_dir.joinpath("Scripts")
  70. return self._base_dir.joinpath("bin")
  71. @property
  72. def _site_packages(self):
  73. if self.settings.os == "Windows":
  74. return self._base_dir.joinpath("Lib", "site-packages")
  75. py_version = Version(self.dependencies["cpython"].ref.version)
  76. return self._base_dir.joinpath("lib", f"python{py_version.major}.{py_version.minor}", "site-packages")
  77. @property
  78. def _py_interp(self):
  79. py_interp = self._script_dir.joinpath(Path(self.deps_user_info["cpython"].python).name)
  80. if self.settings.os == "Windows":
  81. py_interp = Path(*[f'"{p}"' if " " in p else p for p in py_interp.parts])
  82. return py_interp
  83. @property
  84. def _pyinstaller_spec_arch(self):
  85. if self.settings.os == "Macos":
  86. if self.settings.arch == "armv8":
  87. return "'arm64'"
  88. return "'x86_64'"
  89. return "None"
  90. def _conan_installs(self):
  91. self.output.info("Collecting conan installs")
  92. conan_installs = {}
  93. # list of conan installs
  94. for dependency in self.dependencies.host.values():
  95. conan_installs[dependency.ref.name] = {
  96. "version": str(dependency.ref.version),
  97. "revision": dependency.ref.revision
  98. }
  99. return conan_installs
  100. def _python_installs(self):
  101. self.output.info("Collecting python installs")
  102. python_installs = {}
  103. temp_exec = "temp.py"
  104. code = f"import importlib.metadata; print(';'.join([(package.metadata['Name']+','+ package.metadata['Version']) for package in importlib.metadata.distributions()]))"
  105. save(self, temp_exec, code)
  106. buffer = StringIO()
  107. self.run(f"""python {temp_exec}""", env = "virtual_python_env", stdout = buffer)
  108. rm(self, temp_exec, ".")
  109. packages = str(buffer.getvalue()).strip('\r\n').split(";")
  110. for package in packages:
  111. name, version = package.split(",")
  112. python_installs[name] = {"version": version}
  113. return python_installs
  114. def _generate_cura_version(self, location):
  115. with open(os.path.join(self.recipe_folder, "CuraVersion.py.jinja"), "r") as f:
  116. cura_version_py = Template(f.read())
  117. # If you want a specific Cura version to show up on the splash screen add the user configuration `user.cura:version=VERSION`
  118. # the global.conf, profile, package_info (of dependency) or via the cmd line `-c user.cura:version=VERSION`
  119. cura_version = Version(self.conf.get("user.cura:version", default = self.version, check_type = str))
  120. pre_tag = f"-{cura_version.pre}" if cura_version.pre else ""
  121. build_tag = f"+{cura_version.build}" if cura_version.build else ""
  122. internal_tag = f"+internal" if self.options.internal else ""
  123. cura_version = f"{cura_version.major}.{cura_version.minor}.{cura_version.patch}{pre_tag}{build_tag}{internal_tag}"
  124. self.output.info(f"Write CuraVersion.py to {self.recipe_folder}")
  125. with open(os.path.join(location, "CuraVersion.py"), "w") as f:
  126. f.write(cura_version_py.render(
  127. cura_app_name = self.name,
  128. cura_app_display_name = self._app_name,
  129. cura_version = cura_version,
  130. cura_version_full = self.version,
  131. cura_build_type = "Enterprise" if self.options.enterprise else "",
  132. cura_debug_mode = self.options.cura_debug_mode,
  133. cura_cloud_api_root = self.conan_data["urls"][self._urls]["cloud_api_root"],
  134. cura_cloud_api_version = self.options.cloud_api_version,
  135. cura_cloud_account_api_root = self.conan_data["urls"][self._urls]["cloud_account_api_root"],
  136. cura_marketplace_root = self.conan_data["urls"][self._urls]["marketplace_root"],
  137. cura_digital_factory_url = self.conan_data["urls"][self._urls]["digital_factory_url"],
  138. cura_latest_url=self.conan_data["urls"][self._urls]["cura_latest_url"],
  139. conan_installs=self._conan_installs(),
  140. python_installs=self._python_installs(),
  141. ))
  142. def _delete_unwanted_binaries(self, root):
  143. dynamic_binary_file_exts = [".so", ".dylib", ".dll", ".pyd", ".pyi"]
  144. prohibited = [
  145. "qt5compat",
  146. "qtcharts",
  147. "qtcoap",
  148. "qtdatavis3d",
  149. "qtlottie",
  150. "qtmqtt",
  151. "qtnetworkauth",
  152. "qtquick3d",
  153. "qtquick3dphysics",
  154. "qtquicktimeline",
  155. "qtvirtualkeyboard",
  156. "qtwayland"
  157. ]
  158. forbiddens = [x.encode() for x in prohibited]
  159. to_remove_files = []
  160. to_remove_dirs = []
  161. for root, dir_, files in os.walk(root):
  162. for filename in files:
  163. if not any([(x in filename) for x in dynamic_binary_file_exts]):
  164. continue
  165. pathname = os.path.join(root, filename)
  166. still_exist = True
  167. for forbidden in prohibited:
  168. if forbidden.lower() in str(pathname).lower():
  169. to_remove_files.append(pathname)
  170. still_exist = False
  171. break
  172. if not still_exist:
  173. continue
  174. with open(pathname, "rb") as file:
  175. bytez = file.read().lower()
  176. for forbidden in forbiddens:
  177. if bytez.find(forbidden) >= 0:
  178. to_remove_files.append(pathname)
  179. for dirname in dir_:
  180. for forbidden in prohibited:
  181. if forbidden.lower() == str(dirname).lower():
  182. pathname = os.path.join(root, dirname)
  183. to_remove_dirs.append(pathname)
  184. break
  185. for file in to_remove_files:
  186. try:
  187. os.remove(file)
  188. print(f"deleted file: {file}")
  189. except Exception as ex:
  190. print(f"WARNING: Attempt to delete file {file} results in: {str(ex)}")
  191. for dir_ in to_remove_dirs:
  192. try:
  193. rmdir(self, dir_)
  194. print(f"deleted dir_: {dir_}")
  195. except Exception as ex:
  196. print(f"WARNING: Attempt to delete folder {dir_} results in: {str(ex)}")
  197. def _generate_pyinstaller_spec(self, location, entrypoint_location, icon_path, entitlements_file, cura_source_folder):
  198. pyinstaller_metadata = self.conan_data["pyinstaller"]
  199. datas = []
  200. for data in pyinstaller_metadata["datas"].values():
  201. if (not self.options.internal and data.get("internal", False)) or (not self.options.enterprise and data.get("enterprise_only", False)):
  202. continue
  203. if "oses" in data and self.settings.os not in data["oses"]:
  204. continue
  205. if "package" in data: # get the paths from conan package
  206. if data["package"] == self.name:
  207. src_path = str(Path(cura_source_folder, data["src"]))
  208. else:
  209. if data["package"] not in self.dependencies:
  210. raise ConanException(f"Required package {data['package']} does not exist as a dependency")
  211. package_folder = self.dependencies[data['package']].package_folder
  212. if package_folder is None:
  213. raise ConanException(f"Unable to find package_folder for {data['package']}, check that it has not been skipped")
  214. src_path = os.path.join(self.dependencies[data["package"]].package_folder, data["src"])
  215. elif "root" in data: # get the paths relative from the install folder
  216. src_path = os.path.join(self.install_folder, data["root"], data["src"])
  217. else:
  218. raise ConanException("Misformatted conan data for pyinstaller datas, expected either package or root option")
  219. if not Path(src_path).exists():
  220. raise ConanException(f"Missing folder {src_path} for pyinstaller data {data}")
  221. datas.append((str(src_path), data["dst"]))
  222. binaries = []
  223. for binary in pyinstaller_metadata["binaries"].values():
  224. if "package" in binary: # get the paths from conan package
  225. src_path = os.path.join(self.dependencies[binary["package"]].package_folder, binary["src"])
  226. elif "root" in binary: # get the paths relative from the sourcefolder
  227. src_path = str(Path(self.source_folder, binary["root"], binary["src"]))
  228. if self.settings.os == "Windows":
  229. src_path = src_path.replace("\\", "\\\\")
  230. else:
  231. raise ConanException("Misformatted conan data for pyinstaller binaries, expected either package or root option")
  232. if not Path(src_path).exists():
  233. raise ConanException(f"Missing folder {src_path} for pyinstaller binary {binary}")
  234. for bin in Path(src_path).glob(binary["binary"] + "*[.exe|.dll|.so|.dylib|.so.]*"):
  235. binaries.append((str(bin), binary["dst"]))
  236. for bin in Path(src_path).glob(binary["binary"]):
  237. binaries.append((str(bin), binary["dst"]))
  238. # Make sure all Conan dependencies which are shared are added to the binary list for pyinstaller
  239. for _, dependency in self.dependencies.host.items():
  240. for bin_paths in dependency.cpp_info.bindirs:
  241. binaries.extend([(f"{p}", ".") for p in Path(bin_paths).glob("**/*.dll")])
  242. for lib_paths in dependency.cpp_info.libdirs:
  243. binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.so*")])
  244. binaries.extend([(f"{p}", ".") for p in Path(lib_paths).glob("**/*.dylib*")])
  245. # Copy dynamic libs from lib path
  246. binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.dylib*")])
  247. binaries.extend([(f"{p}", ".") for p in Path(self._base_dir.joinpath("lib")).glob("**/*.so*")])
  248. # Collect all dll's from PyQt6 and place them in the root
  249. binaries.extend([(f"{p}", ".") for p in Path(self._site_packages, "PyQt6", "Qt6").glob("**/*.dll")])
  250. with open(os.path.join(self.recipe_folder, "UltiMaker-Cura.spec.jinja"), "r") as f:
  251. pyinstaller = Template(f.read())
  252. version = self.conf.get("user.cura:version", default = self.version, check_type = str)
  253. cura_version = Version(version)
  254. # filter all binary files in binaries on the blacklist
  255. blacklist = pyinstaller_metadata["blacklist"]
  256. filtered_binaries = [b for b in binaries if not any([all([(part in b[0].lower()) for part in parts]) for parts in blacklist])]
  257. # In case the installer isn't actually pyinstaller (Windows at the moment), outright remove the offending files:
  258. specifically_delete = set(binaries) - set(filtered_binaries)
  259. for (unwanted_path, _) in specifically_delete:
  260. try:
  261. os.remove(unwanted_path)
  262. print(f"delete: {unwanted_path}")
  263. except Exception as ex:
  264. print(f"WARNING: Attempt to delete binary {unwanted_path} results in: {str(ex)}")
  265. # Write the actual file:
  266. with open(os.path.join(location, "UltiMaker-Cura.spec"), "w") as f:
  267. f.write(pyinstaller.render(
  268. name = str(self.options.display_name).replace(" ", "-"),
  269. display_name = self._app_name,
  270. entrypoint = entrypoint_location,
  271. datas = datas,
  272. binaries = filtered_binaries,
  273. venv_script_path = str(self._script_dir),
  274. hiddenimports = pyinstaller_metadata["hiddenimports"],
  275. collect_all = pyinstaller_metadata["collect_all"],
  276. icon = icon_path,
  277. entitlements_file = entitlements_file,
  278. osx_bundle_identifier = "'nl.ultimaker.cura'" if self.settings.os == "Macos" else "None",
  279. upx = str(self.settings.os == "Windows"),
  280. strip = False, # This should be possible on Linux and MacOS but, it can also cause issues on some distributions. Safest is to disable it for now
  281. target_arch = self._pyinstaller_spec_arch,
  282. macos = self.settings.os == "Macos",
  283. version = f"'{version}'",
  284. short_version = f"'{cura_version.major}.{cura_version.minor}.{cura_version.patch}'",
  285. ))
  286. def export(self):
  287. update_conandata(self, {"version": self.version})
  288. def export_sources(self):
  289. copy(self, "*", os.path.join(self.recipe_folder, "plugins"), os.path.join(self.export_sources_folder, "plugins"))
  290. copy(self, "*", os.path.join(self.recipe_folder, "resources"), os.path.join(self.export_sources_folder, "resources"), excludes = "*.mo")
  291. copy(self, "*", os.path.join(self.recipe_folder, "tests"), os.path.join(self.export_sources_folder, "tests"))
  292. copy(self, "*", os.path.join(self.recipe_folder, "cura"), os.path.join(self.export_sources_folder, "cura"), excludes="CuraVersion.py")
  293. copy(self, "*", os.path.join(self.recipe_folder, "packaging"), os.path.join(self.export_sources_folder, "packaging"))
  294. copy(self, "*", os.path.join(self.recipe_folder, ".run_templates"), os.path.join(self.export_sources_folder, ".run_templates"))
  295. copy(self, "cura_app.py", self.recipe_folder, self.export_sources_folder)
  296. def validate(self):
  297. if self.options.i18n_extract and self.settings.os == "Windows" and not self.conf.get("tools.microsoft.bash:path", check_type=str):
  298. raise ConanInvalidConfiguration("Unable to extract translations on Windows without Bash installed")
  299. def requirements(self):
  300. for req in self.conan_data["requirements"]:
  301. if self.options.internal and "fdm_materials" in req:
  302. continue
  303. self.requires(req)
  304. if self.options.internal:
  305. for req in self.conan_data["requirements_internal"]:
  306. self.requires(req)
  307. if self.options.enterprise:
  308. for req in self.conan_data["requirements_enterprise"]:
  309. self.requires(req)
  310. self.requires("cpython/3.12.2")
  311. def layout(self):
  312. self.folders.source = "."
  313. self.folders.build = "build"
  314. self.folders.generators = os.path.join(self.folders.build, "generators")
  315. self.cpp.package.libdirs = [os.path.join("site-packages", "cura")]
  316. self.cpp.package.bindirs = ["bin"]
  317. self.cpp.package.resdirs = ["resources", "plugins", "packaging"]
  318. def generate(self):
  319. copy(self, "cura_app.py", self.source_folder, str(self._script_dir))
  320. self._generate_cura_version(str(Path(self.source_folder, "cura")))
  321. # Copy CuraEngine.exe to bindirs of Virtual Python Environment
  322. curaengine = self.dependencies["curaengine"].cpp_info
  323. copy(self, "CuraEngine.exe", curaengine.bindirs[0], self.source_folder, keep_path = False)
  324. copy(self, "CuraEngine", curaengine.bindirs[0], self.source_folder, keep_path = False)
  325. # Copy the external plugins that we want to bundle with Cura
  326. if self.options.enterprise:
  327. rmdir(self, str(Path(self.source_folder, "plugins", "NativeCADplugin")))
  328. native_cad_plugin = self.dependencies["native_cad_plugin"].cpp_info
  329. copy(self, "*", native_cad_plugin.resdirs[0], str(Path(self.source_folder, "plugins", "NativeCADplugin")), keep_path = True)
  330. copy(self, "bundled_*.json", native_cad_plugin.resdirs[1], str(Path(self.source_folder, "resources", "bundled_packages")), keep_path = False)
  331. # Copy resources of cura_binary_data
  332. cura_binary_data = self.dependencies["cura_binary_data"].cpp_info
  333. copy(self, "*", cura_binary_data.resdirs[0], str(self._share_dir.joinpath("cura")), keep_path = True)
  334. copy(self, "*", cura_binary_data.resdirs[1], str(self._share_dir.joinpath("uranium")), keep_path = True)
  335. if self.settings.os == "Windows":
  336. copy(self, "*", cura_binary_data.resdirs[2], str(self._share_dir.joinpath("windows")), keep_path = True)
  337. for dependency in self.dependencies.host.values():
  338. for bindir in dependency.cpp_info.bindirs:
  339. self._delete_unwanted_binaries(bindir)
  340. copy(self, "*.dll", bindir, str(self._site_packages), keep_path = False)
  341. for libdir in dependency.cpp_info.libdirs:
  342. self._delete_unwanted_binaries(libdir)
  343. copy(self, "*.pyd", libdir, str(self._site_packages), keep_path = False)
  344. copy(self, "*.pyi", libdir, str(self._site_packages), keep_path = False)
  345. copy(self, "*.dylib", libdir, str(self._base_dir.joinpath("lib")), keep_path = False)
  346. # Copy materials (flat)
  347. rmdir(self, str(Path(self.source_folder, "resources", "materials")))
  348. fdm_materials = self.dependencies["fdm_materials"].cpp_info
  349. copy(self, "*", fdm_materials.resdirs[0], self.source_folder)
  350. # Copy internal resources
  351. if self.options.internal:
  352. cura_private_data = self.dependencies["cura_private_data"].cpp_info
  353. copy(self, "*", cura_private_data.resdirs[0], str(self._share_dir.joinpath("cura")))
  354. if self.options.i18n_extract:
  355. vb = VirtualBuildEnv(self)
  356. vb.generate()
  357. pot = self.python_requires["translationextractor"].module.ExtractTranslations(self)
  358. pot.generate()
  359. def build(self):
  360. if self.settings.os == "Windows" and not self.conf.get("tools.microsoft.bash:path", check_type=str):
  361. self.output.warning("Skipping generation of binary translation files because Bash could not be found and is required")
  362. return
  363. for po_file in Path(self.source_folder, "resources", "i18n").glob("**/*.po"):
  364. mo_file = Path(self.build_folder, po_file.with_suffix('.mo').relative_to(self.source_folder))
  365. mo_file = mo_file.parent.joinpath("LC_MESSAGES", mo_file.name)
  366. mkdir(self, str(unix_path(self, Path(mo_file).parent)))
  367. self.run(f"msgfmt {po_file} -o {mo_file} -f", env="conanbuild")
  368. def deploy(self):
  369. ''' Note: this deploy step is actually used to prepare for building a Cura distribution with pyinstaller, which is not
  370. the original purpose in the Conan philosophy '''
  371. copy(self, "*", os.path.join(self.package_folder, self.cpp.package.resdirs[2]), os.path.join(self.deploy_folder, "packaging"), keep_path = True)
  372. # Copy resources of Cura (keep folder structure) needed by pyinstaller to determine the module structure
  373. copy(self, "*", os.path.join(self.package_folder, self.cpp_info.bindirs[0]), str(self._base_dir), keep_path = False)
  374. copy(self, "*", os.path.join(self.package_folder, self.cpp_info.libdirs[0]), str(self._site_packages.joinpath("cura")), keep_path = True)
  375. copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[0]), str(self._share_dir.joinpath("cura", "resources")), keep_path = True)
  376. copy(self, "*", os.path.join(self.package_folder, self.cpp_info.resdirs[1]), str(self._share_dir.joinpath("cura", "plugins")), keep_path = True)
  377. # Copy the cura_resources resources from the package
  378. rm(self, "conanfile.py", os.path.join(self.package_folder, self.cpp.package.resdirs[0]))
  379. cura_resources = self.dependencies["cura_resources"].cpp_info
  380. for res_dir in cura_resources.resdirs:
  381. copy(self, "*", res_dir, str(self._share_dir.joinpath("cura", "resources", Path(res_dir).name)), keep_path = True)
  382. # Copy resources of Uranium (keep folder structure)
  383. uranium = self.dependencies["uranium"].cpp_info
  384. copy(self, "*", uranium.resdirs[0], str(self._share_dir.joinpath("uranium", "resources")), keep_path = True)
  385. copy(self, "*", uranium.resdirs[1], str(self._share_dir.joinpath("uranium", "plugins")), keep_path = True)
  386. copy(self, "*", uranium.libdirs[0], str(self._site_packages.joinpath("UM")), keep_path = True)
  387. # Generate the GitHub Action version info Environment
  388. version = self.conf.get("user.cura:version", default = self.version, check_type = str)
  389. cura_version = Version(version)
  390. env_prefix = "Env:" if self.settings.os == "Windows" else ""
  391. activate_github_actions_version_env = Template(r"""echo "CURA_VERSION_MAJOR={{ cura_version_major }}" >> ${{ env_prefix }}GITHUB_ENV
  392. echo "CURA_VERSION_MINOR={{ cura_version_minor }}" >> ${{ env_prefix }}GITHUB_ENV
  393. echo "CURA_VERSION_PATCH={{ cura_version_patch }}" >> ${{ env_prefix }}GITHUB_ENV
  394. echo "CURA_VERSION_BUILD={{ cura_version_build }}" >> ${{ env_prefix }}GITHUB_ENV
  395. echo "CURA_VERSION_FULL={{ cura_version_full }}" >> ${{ env_prefix }}GITHUB_ENV
  396. echo "CURA_APP_NAME={{ cura_app_name }}" >> ${{ env_prefix }}GITHUB_ENV
  397. """).render(cura_version_major = cura_version.major,
  398. cura_version_minor = cura_version.minor,
  399. cura_version_patch = cura_version.patch,
  400. cura_version_build = cura_version.build if cura_version.build != "" else "0",
  401. cura_version_full = self.version,
  402. cura_app_name = self._app_name,
  403. env_prefix = env_prefix)
  404. ext = ".sh" if self.settings.os != "Windows" else ".ps1"
  405. save(self, os.path.join(self._script_dir, f"activate_github_actions_version_env{ext}"), activate_github_actions_version_env)
  406. self._generate_cura_version(os.path.join(self._site_packages, "cura"))
  407. self._delete_unwanted_binaries(self._site_packages)
  408. self._delete_unwanted_binaries(self.package_folder)
  409. self._delete_unwanted_binaries(self._base_dir)
  410. self._delete_unwanted_binaries(self._share_dir)
  411. entitlements_file = "'{}'".format(Path(self.deploy_folder, "packaging", "MacOS", "cura.entitlements"))
  412. self._generate_pyinstaller_spec(location = self.deploy_folder,
  413. entrypoint_location = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.bindirs[0], self.conan_data["pyinstaller"]["runinfo"]["entrypoint"])).replace("\\", "\\\\"),
  414. icon_path = "'{}'".format(os.path.join(self.package_folder, self.cpp_info.resdirs[2], self.conan_data["pyinstaller"]["icon"][str(self.settings.os)])).replace("\\", "\\\\"),
  415. entitlements_file = entitlements_file if self.settings.os == "Macos" else "None",
  416. cura_source_folder = self.package_folder)
  417. def package(self):
  418. copy(self, "cura_app.py", src = self.source_folder, dst = os.path.join(self.package_folder, self.cpp.package.bindirs[0]))
  419. copy(self, "*", src = os.path.join(self.source_folder, "cura"), dst = os.path.join(self.package_folder, self.cpp.package.libdirs[0]))
  420. copy(self, "*", src = os.path.join(self.source_folder, "resources"), dst = os.path.join(self.package_folder, self.cpp.package.resdirs[0]))
  421. copy(self, "*.mo", os.path.join(self.build_folder, "resources"), os.path.join(self.package_folder, "resources"))
  422. copy(self, "*", src = os.path.join(self.source_folder, "plugins"), dst = os.path.join(self.package_folder, self.cpp.package.resdirs[1]))
  423. copy(self, "*", src = os.path.join(self.source_folder, "packaging"), dst = os.path.join(self.package_folder, self.cpp.package.resdirs[2]))
  424. copy(self, "pip_requirements_*.txt", src = self.generators_folder, dst = os.path.join(self.package_folder, self.cpp.package.resdirs[-1]))
  425. # Remove the fdm_materials from the package
  426. rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[0], "materials"))
  427. # Remove the cura_resources resources from the package
  428. rm(self, "conanfile.py", os.path.join(self.package_folder, self.cpp.package.resdirs[0]))
  429. cura_resources = self.dependencies["cura_resources"].cpp_info
  430. for res_dir in cura_resources.resdirs:
  431. rmdir(self, os.path.join(self.package_folder, self.cpp.package.resdirs[0], Path(res_dir).name))
  432. def package_info(self):
  433. self.runenv_info.append_path("PYTHONPATH", os.path.join(self.package_folder, "site-packages"))
  434. self.runenv_info.append_path("PYTHONPATH", os.path.join(self.package_folder, "plugins"))
  435. def package_id(self):
  436. self.info.options.rm_safe("enable_i18n")