updatefiles 5.2 KB

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