make_universal.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. #!/usr/bin/env python
  2. import sys
  3. import os
  4. import subprocess
  5. # A general note: We first produce a x86_64 and a arm64 app package
  6. # and then merge them together instead of compiling the desktop client
  7. # with the CMake option CMAKE_OSX_ARCHITECTURES="x86_64;arm64" because
  8. # macdeployqt can not handle universal binaries well. In the future
  9. # with Qt6 this might change and this script will become obsolete.
  10. def usage(program_name):
  11. print("Creates a universal app package from a x86_64 and a arm64 app package.")
  12. print("Usage: {} x86_64_app_file arm64_app_file output_directory".format(program_name))
  13. print("Example: {} some_dir/Nextcloud.app some_other_dir/Nextcloud.app output_dir".format(program_name))
  14. def execute(command):
  15. return subprocess.check_output(command)
  16. def path_relative_to_package(app_package_file_path, file_path):
  17. if file_path.startswith(app_package_file_path):
  18. relative_path = file_path[len(app_package_file_path):]
  19. if relative_path.startswith("/"):
  20. return relative_path[1:]
  21. return relative_path
  22. return file_path
  23. def is_executable(file_path):
  24. output = str(execute(["file", file_path]))
  25. if (("Mach-O 64-bit dynamically linked shared library" in output)
  26. or ("Mach-O 64-bit executable" in output)):
  27. return True
  28. return False
  29. if __name__ == "__main__":
  30. if len(sys.argv) != 4:
  31. usage(sys.argv[0])
  32. sys.exit(1)
  33. x86_64_app_file = sys.argv[1]
  34. if not os.path.exists(x86_64_app_file):
  35. print("Can't create universal: Path {} does not exist".format(x86_64_app_file))
  36. sys.exit(1)
  37. arm64_app_file = sys.argv[2]
  38. if not os.path.exists(arm64_app_file):
  39. print("Can't create universal: Path {} does not exist".format(arm64_app_file))
  40. sys.exit(1)
  41. output_dir = sys.argv[3]
  42. # Copy the Arm64 variant to the output location if possible
  43. if not os.path.exists(output_dir):
  44. os.makedirs(output_dir)
  45. app_file_name = os.path.basename(arm64_app_file)
  46. universal_app_file = os.path.join(output_dir, app_file_name)
  47. if os.path.exists(universal_app_file):
  48. print("Can't create universal: Path {} already exists".format(universal_app_file))
  49. sys.exit(1)
  50. execute(["cp", "-a", arm64_app_file, output_dir])
  51. # Now walk through the copied arm64 version and replace the binaries
  52. for root, dirs, files in os.walk(universal_app_file):
  53. for f in files:
  54. absolute_file_path = os.path.join(root, f)
  55. root_relative = path_relative_to_package(universal_app_file, root)
  56. x86_64_absolute_path = os.path.join(x86_64_app_file, root_relative, f)
  57. arm64_absolute_path = os.path.join(arm64_app_file, root_relative, f)
  58. if os.path.islink(absolute_file_path) or not is_executable(absolute_file_path):
  59. continue
  60. try:
  61. print(f"Going to merge {arm64_absolute_path} with {x86_64_absolute_path} into {absolute_file_path}")
  62. execute(["lipo", "-create", "-output", absolute_file_path, arm64_absolute_path, x86_64_absolute_path])
  63. print(execute(["lipo", "-info", absolute_file_path]))
  64. except:
  65. print(f"Could not merge {arm64_absolute_path} with {x86_64_absolute_path} into {absolute_file_path}!")
  66. print("Finished :)")