create_windows_msi.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. # Copyright (c) 2022 UltiMaker
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import argparse # Command line arguments parsing and help.
  4. import os
  5. import shutil
  6. import subprocess
  7. import uuid
  8. from datetime import datetime
  9. from pathlib import Path
  10. from jinja2 import Template
  11. def work_path(filename: Path) -> Path:
  12. if not filename.is_absolute():
  13. return Path(os.getcwd(), filename.parent)
  14. else:
  15. return filename.parent
  16. def generate_wxs(source_path: Path, dist_path: Path, filename: Path, app_name: str):
  17. source_loc = Path(os.getcwd(), source_path)
  18. dist_loc = Path(os.getcwd(), dist_path)
  19. work_loc = work_path(filename)
  20. work_loc.mkdir(parents=True, exist_ok=True)
  21. jinja_template_path = Path(source_loc.joinpath("packaging", "msi", "UltiMaker-Cura.wxs.jinja"))
  22. with open(jinja_template_path, "r") as f:
  23. template = Template(f.read())
  24. wxs_content = template.render(
  25. app_name=f"{app_name}",
  26. main_app="UltiMaker-Cura.exe",
  27. version=os.getenv('CURA_VERSION_FULL'),
  28. version_major=os.environ.get("CURA_VERSION_MAJOR"),
  29. version_minor=os.environ.get("CURA_VERSION_MINOR"),
  30. version_patch=os.environ.get("CURA_VERSION_PATCH"),
  31. company="UltiMaker",
  32. web_site="https://ultimaker.com",
  33. year=datetime.now().year,
  34. upgrade_code=str(uuid.uuid5(uuid.NAMESPACE_DNS, app_name)),
  35. cura_license_file=str(source_loc.joinpath("packaging", "msi", "cura_license.rtf")),
  36. cura_banner_top=str(source_loc.joinpath("packaging", "msi", "banner_top.bmp")),
  37. cura_banner_side=str(source_loc.joinpath("packaging", "msi", "banner_side.bmp")),
  38. cura_icon=str(source_loc.joinpath("packaging", "icons", "Cura.ico")),
  39. )
  40. with open(work_loc.joinpath("UltiMaker-Cura.wxs"), "w") as f:
  41. f.write(wxs_content)
  42. try:
  43. shutil.copy(source_loc.joinpath("packaging", "msi", "ExcludeComponents.xslt"),
  44. work_loc.joinpath("ExcludeComponents.xslt"))
  45. except shutil.SameFileError:
  46. pass
  47. def cleanup_artifacts(dist_path: Path):
  48. dist_loc = Path(os.getcwd(), dist_path)
  49. dirt = [d for d in dist_loc.rglob("__pycache__") if d.is_dir()]
  50. dirt += [d for d in dist_loc.rglob("*.dist-info") if d.is_dir()]
  51. for d in dirt:
  52. if d.exists():
  53. shutil.rmtree(d, ignore_errors=True)
  54. def build(dist_path: Path, filename: Path):
  55. dist_loc = Path(os.getcwd(), dist_path)
  56. work_loc = work_path(filename)
  57. wxs_loc = work_loc.joinpath("UltiMaker-Cura.wxs")
  58. heat_loc = work_loc.joinpath("HeatFile.wxs")
  59. exclude_components_loc = work_loc.joinpath("ExcludeComponents.xslt")
  60. build_loc = work_loc.joinpath("build_msi")
  61. heat_command = ["heat",
  62. "dir", f"{dist_loc.as_posix()}\\",
  63. "-dr", "APPLICATIONFOLDER",
  64. "-cg", "NewFilesGroup",
  65. "-sw5150", # Don't pollute logs with warnings from auto generated content
  66. "-gg",
  67. "-g1",
  68. "-sf",
  69. "-srd",
  70. "-var", "var.CuraDir",
  71. "-t", f"{exclude_components_loc.as_posix()}",
  72. "-out", f"{heat_loc.as_posix()}"]
  73. subprocess.call(heat_command)
  74. build_command = ["candle",
  75. "-arch", "x64",
  76. f"-dCuraDir={dist_loc}\\",
  77. "-ext", "WixFirewallExtension",
  78. "-out", f"{build_loc.as_posix()}\\",
  79. f"{wxs_loc.as_posix()}",
  80. f"{heat_loc.as_posix()}"]
  81. subprocess.call(build_command)
  82. link_command = ["light",
  83. f"{build_loc.joinpath(wxs_loc.name).with_suffix('.wixobj')}",
  84. f"{build_loc.joinpath(heat_loc.name).with_suffix('.wixobj')}",
  85. "-sw1076", # Don't pollute logs with warnings from auto generated content
  86. "-dcl:high", # Use high compression ratio
  87. "-sval", # Disable ICE validation otherwise the CI complains
  88. "-ext", "WixUIExtension",
  89. "-ext", "WixFirewallExtension",
  90. "-out", f"{work_loc.joinpath(filename.name)}"]
  91. subprocess.call(link_command)
  92. if __name__ == "__main__":
  93. parser = argparse.ArgumentParser(description="Create Windows msi installer of Cura.")
  94. parser.add_argument("source_path", type=Path, help="Path to Conan install Cura folder.")
  95. parser.add_argument("dist_path", type=Path, help="Path to Pyinstaller dist folder")
  96. parser.add_argument("filename", type=Path,
  97. help="Filename of the exe (e.g. 'UltiMaker-Cura-5.1.0-beta-Windows-X64.msi')")
  98. parser.add_argument("name", type=str, help="App name (e.g. 'UltiMaker Cura')")
  99. args = parser.parse_args()
  100. generate_wxs(args.source_path.resolve(), args.dist_path.resolve(), args.filename.resolve(), args.name)
  101. cleanup_artifacts(args.dist_path.resolve())
  102. build(args.dist_path.resolve(), args.filename)