create_windows_msi.py 5.1 KB

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