summaryrefslogtreecommitdiff
path: root/package.py
diff options
context:
space:
mode:
authorGravatar Clément Pit--Claudel <clement.pitclaudel@live.com>2015-08-23 04:29:59 -0700
committerGravatar Clément Pit--Claudel <clement.pitclaudel@live.com>2015-08-23 04:29:59 -0700
commit94e1770b5aa240a807955e2855253c0f3ef895a7 (patch)
treefd5921e0fbbabe8e83630aaca53ad3313bc66b32 /package.py
parentf09b14f4b3f3915343b1279da286351ab5348eac (diff)
Write a new packaging script for Dafny
This is intended to superseed the PrepareDafnyZip.bat script. I tried doing this as an msbuild config, then as a makefile, but the first one was too cumbersome and the second one required too many dependencies to work properly (and parsing JSON from a makefile is not fun). The new script calls the Github API to retrieve the description of the latest z3 release, downloads the zip corresponding to each supported platfom, and packages one copy of Dafny for each copy of z3 that the latest release contains. The next step will be to make Dafny load its binary from the z3 folder.
Diffstat (limited to 'package.py')
-rw-r--r--package.py198
1 files changed, 198 insertions, 0 deletions
diff --git a/package.py b/package.py
new file mode 100644
index 00000000..22ed232c
--- /dev/null
+++ b/package.py
@@ -0,0 +1,198 @@
+import argparse
+import json
+import os
+from os import path
+import re
+import urllib.request
+import sys
+import zipfile
+import subprocess
+import shutil
+
+# Configuration
+
+RELEASES_URL = "https://api.github.com/repos/Z3Prover/z3/releases/latest"
+RELEASE_REGEXP = r"^(?P<directory>z3-[0-9\.]+-(?P<platform>x86|x64)-(?P<os>[a-z0-9\.\-]+)).zip$"
+
+SOURCE_DIRECTORY = "Source"
+BINARIES_DIRECTORY = "Binaries"
+DESTINATION_DIRECTORY = "Package"
+
+Z3_ARCHIVE_PREFIX = path.join("z3", "bin")
+
+DLLs = ["AbsInt",
+ "Basetypes",
+ "CodeContractsExtender",
+ "Concurrency",
+ "Core",
+ "DafnyPipeline",
+ "Doomed",
+ "ExecutionEngine",
+ "Graph",
+ "Houdini",
+ "Model",
+ "ModelViewer",
+ "ParserHelper",
+ "Provers.SMTLib",
+ "VCExpr",
+ "VCGeneration"]
+EXEs = ["Dafny", "DafnyServer"]
+ETCs = ["dafny", "DafnyPrelude.bpl", "DafnyRuntime.cs"]
+
+# Constants
+
+THIS_FILE = path.realpath(__file__)
+ROOT_DIRECTORY = path.dirname(THIS_FILE)
+SOURCE_DIRECTORY = path.join(ROOT_DIRECTORY, SOURCE_DIRECTORY)
+BINARIES_DIRECTORY = path.join(ROOT_DIRECTORY, BINARIES_DIRECTORY)
+DESTINATION_DIRECTORY = path.join(ROOT_DIRECTORY, DESTINATION_DIRECTORY)
+CACHE_DIRECTORY = path.join(DESTINATION_DIRECTORY, "cache")
+
+RELEASE_REGEXP = re.compile(RELEASE_REGEXP, re.IGNORECASE)
+
+MONO = sys.platform not in ("win32", "cygwin")
+DLL_PDB_EXT = ".dll.mdb" if MONO else ".pdb"
+EXE_PDB_EXT = ".exe.mdb" if MONO else ".pdb"
+ARCHIVE_FNAMES = ([dll + ".dll" for dll in DLLs] + [dll + DLL_PDB_EXT for dll in DLLs] +
+ [exe + ".exe" for exe in EXEs] + [exe + EXE_PDB_EXT for exe in EXEs] +
+ ETCs)
+
+# Code
+
+def flush(*args, **kwargs):
+ print(*args, **kwargs)
+ sys.stdout.flush()
+
+class Release:
+ @staticmethod
+ def parse_zip_name(name):
+ m = RELEASE_REGEXP.match(name)
+ if not m:
+ raise Exception("{} does not match RELEASE_REGEXP".format(name))
+ return m.group('platform'), m.group('os'), m.group("directory")
+
+ def __init__(self, js, version):
+ self.z3_name = js["name"]
+ self.size = js["size"]
+ self.url = js["browser_download_url"]
+ self.platform, self.os, self.directory = Release.parse_zip_name(js["name"])
+ self.z3_zip = path.join(CACHE_DIRECTORY, self.z3_name)
+ self.z3_directory = path.join(CACHE_DIRECTORY, self.directory)
+ self.z3_bin_directory = path.join(self.z3_directory, "bin")
+ self.dafny_name = "dafny-{}-{}-{}.zip".format(version, self.platform, self.os)
+ self.dafny_zip = path.join(DESTINATION_DIRECTORY, self.dafny_name)
+
+ @property
+ def cached(self):
+ return path.exists(self.z3_zip) and path.getsize(self.z3_zip) == self.size
+
+ @property
+ def MB(self):
+ return self.size / 1e6
+
+ def download(self):
+ if self.cached:
+ print("cached!")
+ else:
+ flush("downloading {:.2f}MB...".format(self.MB), end=' ')
+ with urllib.request.urlopen(self.url) as reader:
+ with open(self.z3_zip, mode="wb") as writer:
+ writer.write(reader.read())
+ flush("done!")
+
+ def unpack(self):
+ shutil.rmtree(self.z3_directory)
+ with zipfile.ZipFile(self.z3_zip) as archive:
+ archive.extractall(CACHE_DIRECTORY)
+ flush("done!")
+
+ def pack(self):
+ try:
+ os.remove(self.dafny_zip)
+ except FileNotFoundError:
+ pass
+ missing = []
+ with zipfile.ZipFile(self.dafny_zip, 'w', zipfile.ZIP_DEFLATED) as archive:
+ for root, _, files in os.walk(self.z3_bin_directory):
+ for f in files:
+ fpath = path.join(root, f)
+ arcpath = path.join(Z3_ARCHIVE_PREFIX, path.relpath(fpath, self.z3_bin_directory))
+ archive.write(fpath, arcpath)
+ for fname in ARCHIVE_FNAMES:
+ fpath = path.join(BINARIES_DIRECTORY, fname)
+ if path.exists(fpath):
+ archive.write(fpath, fname)
+ else:
+ missing.append(fname)
+ flush("done!")
+ if missing:
+ flush(" WARNING: Not all files were found: {} were missing".format(", ".join(missing)))
+
+def discover(version):
+ flush(" - Getting information about latest release")
+ with urllib.request.urlopen("https://api.github.com/repos/Z3Prover/z3/releases/latest") as reader:
+ js = json.loads(reader.read().decode("utf-8"))
+
+ for release_js in js["assets"]:
+ release = Release(release_js, version)
+ if release.platform == "x64":
+ flush(" + Selecting {} ({:.2f}MB, {})".format(release.z3_name, release.MB, release.size))
+ yield release
+ else:
+ flush(" + Rejecting {}".format(release.z3_name))
+
+def download(releases):
+ flush(" - Downloading {} z3 archives".format(len(releases)))
+ for release in releases:
+ flush(" + {}:".format(release.z3_name), end=' ')
+ release.download()
+
+def unpack(releases):
+ flush(" - Unpacking {} z3 archives".format(len(releases)))
+ for release in releases:
+ flush(" + {}:".format(release.z3_name), end=' ')
+ release.unpack()
+
+def run(cmd):
+ flush(" + {}...".format(" ".join(cmd)), end=' ')
+ retv = subprocess.call(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ if retv != 0:
+ flush("failed!")
+ sys.exit(1)
+ else:
+ flush("done!")
+
+def build():
+ os.chdir(ROOT_DIRECTORY)
+ flush(" - Building")
+ builder = "xbuild" if MONO else "xbuild"
+ run([builder, "Source/Dafny.sln", "/t:Clean"])
+ run([builder, "Source/Dafny.sln", "/p:Configuration=Checked", "/t:Rebuild"])
+
+def pack(releases):
+ flush(" - Packaging {} Dafny archives".format(len(releases)))
+ for release in releases:
+ flush(" + {}:".format(release.dafny_name), end=' ')
+ release.pack()
+
+def parse_arguments():
+ parser = argparse.ArgumentParser(description="Prepare a Dafny release. Configuration is hardcoded; edit the `# Configuration' section of this script to change it.")
+ parser.add_argument("version", help="Version number for this release")
+ return parser.parse_args()
+
+def main():
+ args = parse_arguments()
+ os.makedirs(CACHE_DIRECTORY, exist_ok=True)
+
+ # Z3
+ flush("* Finding, downloading, and unpacking Z3 releases")
+ releases = list(discover(args.version))
+ download(releases)
+ unpack(releases)
+
+ flush("* Building and packaging Dafny")
+ build()
+ pack(releases)
+
+if __name__ == '__main__':
+ main()