updatefiles.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. """
  2. This script can be used to update this repository with the latest information.
  3. It performs the following actions:
  4. - Updates the local clone of the repository
  5. - Downloads the latest setup files from Appveyor
  6. - Updates the README with the latest version info (commit ID and message)
  7. - Uploads changes to the repository
  8. """
  9. import os
  10. import sys
  11. import pickle
  12. import requests
  13. import json
  14. from requests.exceptions import ConnectionError as ConnError
  15. from subprocess import call
  16. ETAGFILE = "etags"
  17. ETAGS = {} # type: dict
  18. DOC = __doc__.replace("This script", f"The [{__file__}]({__file__}) script")
  19. def load_etags():
  20. """
  21. Read in etags file and populates dictionary.
  22. """
  23. try:
  24. with open(ETAGFILE, "rb") as etagfile:
  25. ETAGS.update(pickle.load(etagfile))
  26. except FileNotFoundError:
  27. # print("--> No etags file found. Skipping load.")
  28. pass
  29. def save_etags():
  30. """
  31. Save (potentially new) etags to file.
  32. """
  33. with open(ETAGFILE, "wb") as etagfile:
  34. pickle.dump(ETAGS, etagfile)
  35. def download(url, fname=None):
  36. """
  37. Download a URL if necessary. If the URL's etag matches the existing one,
  38. the download is skipped.
  39. """
  40. if fname is None:
  41. fname = url.split("/")[-1]
  42. print("--> Downloading {} → {}".format(url, fname))
  43. try:
  44. req = requests.get(url, stream=True)
  45. except ConnError:
  46. print("Error while trying to download {}".format(url), file=sys.stderr)
  47. print("Skipping.", file=sys.stderr)
  48. return
  49. size = int(req.headers.get("content-length"))
  50. etag = req.headers.get("etag")
  51. oldet = ETAGS.get(url)
  52. if etag == oldet and os.path.exists(fname):
  53. fod_size = os.path.getsize(fname)
  54. if fod_size == size:
  55. print("File already downloaded. Skipping.", end="\n\n")
  56. return fname
  57. ETAGS[url] = etag
  58. prog = 0
  59. with open(fname, "wb") as dlfile:
  60. for chunk in req.iter_content(chunk_size=256):
  61. dlfile.write(chunk)
  62. prog += len(chunk)
  63. print("\r{:2.1f}%".format(prog / size * 100), end="", flush=True)
  64. print("\nDone!")
  65. print()
  66. return fname
  67. def get_appveyor_info():
  68. # TODO: Check what happens when a build is in progress and so has no
  69. # available artifacts
  70. apiurl = "https://ci.appveyor.com/api/"
  71. account = "G-Node"
  72. project_name = "GinUI"
  73. url = os.path.join(apiurl, "projects", account, project_name)
  74. r = requests.get(url)
  75. projects = json.loads(r.text)
  76. build = projects["build"]
  77. info = dict()
  78. info["json"] = r.text
  79. info["commit"] = build["commitId"]
  80. info["message"] = build["message"]
  81. info["version"] = build["version"]
  82. dlurls = []
  83. for job in build["jobs"]: # should just be one for this project
  84. if job["status"] == "success":
  85. artifacts_url = os.path.join(apiurl, "buildjobs", job["jobId"],
  86. "artifacts")
  87. r = requests.get(artifacts_url)
  88. artifacts = json.loads(r.text)
  89. for a in artifacts:
  90. dlurls.append(os.path.join(apiurl, "buildjobs", job["jobId"],
  91. "artifacts", a["fileName"]))
  92. info["artifacts"] = dlurls
  93. return info
  94. def update_readme(info):
  95. print(f"Latest commit: {info['message']} [{info['commit']}]")
  96. commiturl = (f"https://github.com/G-Node/GinUI/commit/{info['commit']}")
  97. vertext = (f"\n## Version {info['version']}\n\n"
  98. "The current version of the setup files corresponds to:\n\n"
  99. f"- Commit ID: [{info['commit']}]({commiturl})\n"
  100. f"- Message: {info['message']}\n")
  101. scriptdesc = f"\n## {__file__}\n{DOC}"
  102. with open("instructions.md") as instructfile:
  103. instructions = instructfile.read()
  104. with open("README.md", "w") as readme:
  105. readme.write(instructions)
  106. readme.write(vertext)
  107. readme.write(scriptdesc)
  108. def update_verinfo(info):
  109. with open("version", "w") as verfile:
  110. verfile.write(info["version"])
  111. with open("build.json", "w") as jsonfile:
  112. jsonfile.write(info["json"])
  113. def main():
  114. print("Updating local repository")
  115. call(["gin", "download", "--content"])
  116. print("Unlocking file content")
  117. call(["gin", "unlock", "."])
  118. load_etags()
  119. dlfiles = []
  120. avinfo = get_appveyor_info()
  121. artifacts = avinfo["artifacts"]
  122. print(f"Latest build message: {avinfo['message']}")
  123. if not len(artifacts):
  124. print("No artifacts in current build. Perhaps it is still running?")
  125. call(["gin", "lock", "."])
  126. sys.exit("aborting")
  127. print(f"Found {len(artifacts)} artifacts")
  128. for url in artifacts:
  129. dlfiles.append(download(url))
  130. save_etags()
  131. print("Updating README.md")
  132. update_readme(avinfo)
  133. update_verinfo(avinfo)
  134. print("Uploading changes")
  135. # at each run, we expect the following files to change
  136. changedfiles = ["README.md", "etags", "Setup.msi", "setup.exe", "version",
  137. "build.json"]
  138. # any other changes (e.g., changes to this script) should be handled
  139. # manually
  140. call(["gin", "upload", *changedfiles])
  141. if __name__ == "__main__":
  142. main()