sconsdist.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env python3
  2. from flipper.app import App
  3. from os.path import join, exists, relpath
  4. from os import makedirs, walk
  5. from update import Main as UpdateMain
  6. import shutil
  7. import zipfile
  8. import tarfile
  9. from ansi.color import fg
  10. class ProjectDir:
  11. def __init__(self, project_dir):
  12. self.dir = project_dir
  13. parts = project_dir.split("-")
  14. self.target = parts[0]
  15. self.project = parts[1]
  16. self.flavor = parts[2] if len(parts) > 2 else ""
  17. class Main(App):
  18. DIST_FILE_PREFIX = "flipper-z-"
  19. DIST_FOLDER_MAX_NAME_LENGTH = 80
  20. def init(self):
  21. self.subparsers = self.parser.add_subparsers(help="sub-command help")
  22. self.parser_copy = self.subparsers.add_parser(
  23. "copy", help="Copy firmware binaries & metadata"
  24. )
  25. self.parser_copy.add_argument("-p", dest="project", nargs="+", required=True)
  26. self.parser_copy.add_argument("-s", dest="suffix", required=True)
  27. self.parser_copy.add_argument("-r", dest="resources", required=False)
  28. self.parser_copy.add_argument(
  29. "--bundlever",
  30. dest="version",
  31. help="If set, bundle update package for self-update",
  32. required=False,
  33. )
  34. self.parser_copy.add_argument(
  35. "--noclean",
  36. dest="noclean",
  37. action="store_true",
  38. help="Don't clean output directory",
  39. required=False,
  40. )
  41. self.parser_copy.set_defaults(func=self.copy)
  42. def get_project_filename(self, project, filetype):
  43. # Temporary fix
  44. project_name = project.project
  45. if project_name == "firmware":
  46. if filetype == "zip":
  47. project_name = "sdk"
  48. elif filetype != "elf":
  49. project_name = "full"
  50. return f"{self.DIST_FILE_PREFIX}{self.target}-{project_name}-{self.args.suffix}.{filetype}"
  51. def get_dist_filepath(self, filename):
  52. return join(self.output_dir_path, filename)
  53. def copy_single_project(self, project):
  54. obj_directory = join("build", project.dir)
  55. for filetype in ("elf", "bin", "dfu", "json"):
  56. if exists(src_file := join(obj_directory, f"{project.project}.{filetype}")):
  57. shutil.copyfile(
  58. src_file,
  59. self.get_dist_filepath(
  60. self.get_project_filename(project, filetype)
  61. ),
  62. )
  63. if exists(sdk_folder := join(obj_directory, "sdk")):
  64. with zipfile.ZipFile(
  65. self.get_dist_filepath(self.get_project_filename(project, "zip")),
  66. "w",
  67. zipfile.ZIP_DEFLATED,
  68. ) as zf:
  69. for root, dirs, files in walk(sdk_folder):
  70. for file in files:
  71. zf.write(
  72. join(root, file),
  73. relpath(
  74. join(root, file),
  75. sdk_folder,
  76. ),
  77. )
  78. def copy(self):
  79. self.projects = dict(
  80. map(
  81. lambda pd: (pd.project, pd),
  82. map(ProjectDir, self.args.project),
  83. )
  84. )
  85. project_targets = set(map(lambda p: p.target, self.projects.values()))
  86. if len(project_targets) != 1:
  87. self.logger.error(f"Cannot mix targets {project_targets}!")
  88. return 1
  89. self.target = project_targets.pop()
  90. project_flavors = set(map(lambda p: p.flavor, self.projects.values()))
  91. if len(project_flavors) != 1:
  92. self.logger.error(f"Cannot mix flavors {project_flavors}!")
  93. return 2
  94. self.flavor = project_flavors.pop()
  95. dist_dir_components = [self.target]
  96. if self.flavor:
  97. dist_dir_components.append(self.flavor)
  98. self.output_dir_path = join("dist", "-".join(dist_dir_components))
  99. if exists(self.output_dir_path) and not self.args.noclean:
  100. try:
  101. shutil.rmtree(self.output_dir_path)
  102. except Exception as ex:
  103. pass
  104. if not exists(self.output_dir_path):
  105. makedirs(self.output_dir_path)
  106. for project in self.projects.values():
  107. self.copy_single_project(project)
  108. self.logger.info(
  109. fg.green(f"Firmware binaries can be found at:\n\t{self.output_dir_path}")
  110. )
  111. if self.args.version:
  112. bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[
  113. : self.DIST_FOLDER_MAX_NAME_LENGTH
  114. ]
  115. bundle_dir = join(self.output_dir_path, bundle_dir_name)
  116. bundle_args = [
  117. "generate",
  118. "-d",
  119. bundle_dir,
  120. "-v",
  121. self.args.version,
  122. "-t",
  123. self.target,
  124. "--dfu",
  125. self.get_dist_filepath(
  126. self.get_project_filename(self.projects["firmware"], "dfu")
  127. ),
  128. "--stage",
  129. self.get_dist_filepath(
  130. self.get_project_filename(self.projects["updater"], "bin")
  131. ),
  132. ]
  133. if self.args.resources:
  134. bundle_args.extend(
  135. (
  136. "-r",
  137. self.args.resources,
  138. )
  139. )
  140. bundle_args.extend(self.other_args)
  141. if (bundle_result := UpdateMain(no_exit=True)(bundle_args)) == 0:
  142. self.logger.info(
  143. fg.green(
  144. f"Use this directory to self-update your Flipper:\n\t{bundle_dir}"
  145. )
  146. )
  147. # Create tgz archive
  148. with tarfile.open(
  149. join(
  150. self.output_dir_path,
  151. f"{self.DIST_FILE_PREFIX}{bundle_dir_name}.tgz",
  152. ),
  153. "w:gz",
  154. compresslevel=9,
  155. format=tarfile.USTAR_FORMAT,
  156. ) as tar:
  157. tar.add(bundle_dir, arcname=bundle_dir_name)
  158. return bundle_result
  159. return 0
  160. if __name__ == "__main__":
  161. Main()()