package_cross_platform.py 3.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. """Package a self-contained desktop publish. No signing credentials are read."""
  2. import argparse
  3. import hashlib
  4. import os
  5. import platform
  6. from pathlib import Path
  7. import plistlib
  8. import shutil
  9. import subprocess
  10. import tarfile
  11. import zipfile
  12. def package(runtime: str, source: Path, output: Path) -> Path:
  13. source = source.resolve()
  14. output = output.resolve()
  15. if not source.is_dir():
  16. raise ValueError(f"Missing publish directory: {source}")
  17. output.mkdir(parents=True, exist_ok=True)
  18. repo = Path(__file__).resolve().parent.parent
  19. name = f"PCL-Communist-0.1.0-preview-{runtime}"
  20. stage = output / name
  21. if stage.exists():
  22. # This is a generated staging directory with a fixed child name.
  23. if stage.parent != output or stage.is_symlink():
  24. raise ValueError("Unexpected staging path")
  25. shutil.rmtree(stage)
  26. stage.mkdir()
  27. if runtime.startswith("osx-"):
  28. app = stage / "PCL Communist.app"
  29. contents = app / "Contents"
  30. executable_dir = contents / "MacOS"
  31. resources = contents / "Resources"
  32. shutil.copytree(source, executable_dir)
  33. resources.mkdir()
  34. executable = executable_dir / "PCL.Communist.Desktop"
  35. executable.chmod(0o755)
  36. plist = {
  37. "CFBundleExecutable": executable.name,
  38. "CFBundleIdentifier": "org.pclcommunist.launcher",
  39. "CFBundleName": "PCL Communist",
  40. "CFBundleDisplayName": "PCL Communist",
  41. "CFBundlePackageType": "APPL",
  42. "CFBundleShortVersionString": "0.1.0",
  43. "CFBundleVersion": "1",
  44. "LSMinimumSystemVersion": "12.0",
  45. "NSHighResolutionCapable": True,
  46. "NSRequiresAquaSystemAppearance": False,
  47. }
  48. shutil.copyfile(repo / "CrossPlatform/PCL.Communist/Assets/Launcher.icns", resources / "Launcher.icns")
  49. plist["CFBundleIconFile"] = "Launcher.icns"
  50. with (contents / "Info.plist").open("wb") as stream:
  51. plistlib.dump(plist, stream)
  52. shutil.copyfile(repo / "COMMUNIST-AGREEMENT.txt", resources / "COMMUNIST-AGREEMENT.txt")
  53. shutil.copytree(repo / "licenses", resources / "licenses")
  54. # Ad-hoc signing on a Mac makes the local bundle internally consistent; it is not Apple notarization.
  55. if platform.system() == "Darwin":
  56. subprocess.run(["codesign", "--force", "--deep", "--sign", "-", str(app)], check=True)
  57. else:
  58. shutil.copytree(source, stage, dirs_exist_ok=True)
  59. shutil.copyfile(repo / "COMMUNIST-AGREEMENT.txt", stage / "COMMUNIST-AGREEMENT.txt")
  60. shutil.copytree(repo / "licenses", stage / "licenses", dirs_exist_ok=True)
  61. if runtime.startswith("linux-"):
  62. (stage / "PCL.Communist.Desktop").chmod(0o755)
  63. shutil.copyfile(repo / "docs/cross-platform.md", stage / "PREVIEW-README.md")
  64. if runtime.startswith("win-"):
  65. archive = output / f"{name}.zip"
  66. with zipfile.ZipFile(archive, "w", zipfile.ZIP_DEFLATED) as zip_file:
  67. for file in sorted(stage.rglob("*")):
  68. if file.is_file():
  69. zip_file.write(file, file.relative_to(output))
  70. else:
  71. archive = output / f"{name}.tar.gz"
  72. with tarfile.open(archive, "w:gz") as tar_file:
  73. tar_file.add(stage, arcname=stage.name)
  74. with archive.open("rb") as stream:
  75. digest = hashlib.file_digest(stream, "sha256").hexdigest()
  76. archive.with_suffix(archive.suffix + ".sha256").write_text(f"{digest} {archive.name}\n", encoding="utf-8")
  77. print(archive)
  78. return archive
  79. if __name__ == "__main__":
  80. parser = argparse.ArgumentParser()
  81. parser.add_argument("--runtime", required=True, choices=["win-x64", "win-arm64", "linux-x64", "linux-arm64", "osx-x64", "osx-arm64"])
  82. parser.add_argument("--input", required=True, type=Path)
  83. parser.add_argument("--output", required=True, type=Path)
  84. args = parser.parse_args()
  85. package(args.runtime, args.input, args.output)