1import os
2import sys
3import hashlib
4import re
5import subprocess
6import time
7import glob
8import zipfile
9import shutil
10import requests
11import json
12
13# Timeout socket handling
14import socket
15import threading
16import errno
17
18from util import *
19
20try:
21    import zlib
22    compression = zipfile.ZIP_DEFLATED
23except:
24    compression = zipfile.ZIP_STORED
25
26modes = { zipfile.ZIP_DEFLATED: 'deflated',
27          zipfile.ZIP_STORED:   'stored',
28          }
29
30try:
31    # Python 3
32    from urllib.request import urlopen, Request
33    from urllib.error import HTTPError, URLError
34    from urllib.parse import urlparse
35    from http.client import HTTPException
36except ImportError:
37    # Python 2
38    from urllib2 import urlopen, Request, HTTPError, URLError
39    from urlparse import urlparse
40    from httplib import HTTPException
41
42BINTRAY_SNAPSHOT_SERVER_PATH = "https://oss.jfrog.org/artifactory/oss-snapshot-local"
43BINTRAY_RELEASE_SERVER_PATH = "https://oss.jfrog.org/artifactory/oss-release-local"
44BINTRAY_MAVEN_GROUP_PATH = "/org/github/alberthdev/cemu/"
45MAX_ATTEMPTS = 5
46SHA256_STRICT = False
47
48# Solution from Anppa @ StackOverflow
49# http://stackoverflow.com/a/18468750/1094484
50def timeout_http_body_read_to_file(response, fh, timeout = 60):
51    def murha(resp):
52        os.close(resp.fileno())
53        resp.close()
54
55    # set a timer to yank the carpet underneath the blocking read() by closing the os file descriptor
56    t = threading.Timer(timeout, murha, (response,))
57
58    t.start()
59    fh.write(response.read())
60    t.cancel()
61
62def truncate_url(url):
63    if len(url) > 70:
64        truncated_url = url[:34] + ".." + url[len(url) - 34:]
65    else:
66        truncated_url = url
67
68    return truncated_url
69
70def check_file(path):
71    try:
72        test_fh = open(path)
73        test_fh.close()
74        return True
75    except IOError:
76        return False
77
78# Note: suppress_errors will only work on HTTP errors
79# Other errors will be forcefully displayed
80def check_url(url, suppress_errors = True):
81    check_attempts = 0
82    found = False
83
84    while check_attempts <= MAX_ATTEMPTS:
85        # If we aren't on our first download attempt, wait a bit.
86        if check_attempts > 0:
87            print("         !! Download attempt failed, waiting 10s before retry...")
88            print("            (attempt %i/%i)" % (check_attempts + 1, MAX_ATTEMPTS))
89
90            # Wait...
91            time.sleep(10)
92
93        # Open the url
94        try:
95            f = urlopen(url)
96
97            # Everything good!
98            found = True
99            break
100
101        # Error handling...
102        except HTTPError:
103            if not suppress_errors:
104                _, e, _ = sys.exc_info()
105                print("         !! HTTP Error: %i (%s)" % (e.code, url))
106                print("         !! Server said:")
107                err = e.read().decode("utf-8")
108                err = "         !! " + "\n         !! ".join(err.split("\n")).strip()
109                print(err)
110
111            found = False
112            break
113        except URLError:
114            _, e, _ = sys.exc_info()
115            print("         !! URL Error: %s (%s)" % (e.reason, url))
116
117            found = False
118            break
119        except HTTPException:
120            _, e, _ = sys.exc_info()
121            print("         !! HTTP Exception: %s (%s)" % (str(e), url))
122        except socket.error:
123            _, e, _ = sys.exc_info()
124            if e.errno == errno.EBADF:
125                print("         !! Timeout reached: %s (%s)" % (str(e), url))
126            else:
127                print("         !! Socket Exception: %s (%s)" % (str(e), url))
128
129        # Increment attempts
130        check_attempts += 1
131
132    if check_attempts > MAX_ATTEMPTS:
133        print("         !! ERROR: URL check failed, assuming not found!")
134
135    return found
136
137def dlfile(url, dest = None):
138    dl_attempts = 0
139    dest = dest or os.path.basename(url)
140
141    while dl_attempts <= MAX_ATTEMPTS:
142        # If we aren't on our first download attempt, wait a bit.
143        if dl_attempts > 0:
144            print("         !! Download attempt failed, waiting 10s before retry...")
145            print("            (attempt %i/%i)" % (dl_attempts + 1, MAX_ATTEMPTS))
146
147            # Wait...
148            time.sleep(10)
149
150        # Open the url
151        try:
152            f = urlopen(url)
153            print("         -> Downloading:")
154            print("            %s" % truncate_url(url))
155
156            # Open our local file for writing
157            with open(dest, "wb") as local_file:
158                timeout_http_body_read_to_file(f, local_file, timeout = 300)
159                #local_file.write(f.read())
160
161            # Everything good!
162            break
163
164        # Error handling...
165        except HTTPError:
166            _, e, _ = sys.exc_info()
167            print("         !! HTTP Error: %i (%s)" % (e.code, url))
168            print("         !! Server said:")
169            err = e.read().decode("utf-8")
170            err = "         !! " + "\n         !! ".join(err.split("\n")).strip()
171            print(err)
172        except URLError:
173            _, e, _ = sys.exc_info()
174            print("         !! URL Error: %s (%s)" % (e.reason, url))
175        except HTTPException:
176            _, e, _ = sys.exc_info()
177            print("         !! HTTP Exception: %s (%s)" % (str(e), url))
178        except socket.error:
179            _, e, _ = sys.exc_info()
180            if e.errno == errno.EBADF:
181                print("         !! Timeout reached: %s (%s)" % (str(e), url))
182            else:
183                print("         !! Socket Exception: %s (%s)" % (str(e), url))
184
185        # Increment attempts
186        dl_attempts += 1
187
188    if dl_attempts > MAX_ATTEMPTS:
189        print("         !! ERROR: Download failed, exiting!")
190        sys.exit(1)
191
192def generate_file_md5(filename, blocksize=2**20):
193    m = hashlib.md5()
194    with open( filename , "rb" ) as f:
195        while True:
196            buf = f.read(blocksize)
197            if not buf:
198                break
199            m.update( buf )
200    return m.hexdigest()
201
202def generate_file_sha1(filename, blocksize=2**20):
203    m = hashlib.sha1()
204    with open( filename , "rb" ) as f:
205        while True:
206            buf = f.read(blocksize)
207            if not buf:
208                break
209            m.update( buf )
210    return m.hexdigest()
211
212def generate_file_sha256(filename, blocksize=2**20):
213    m = hashlib.sha256()
214    with open( filename , "rb" ) as f:
215        while True:
216            buf = f.read(blocksize)
217            if not buf:
218                break
219            m.update( buf )
220    return m.hexdigest()
221
222def output_md5(filename):
223    md5_result = "%s  %s" % (filename, generate_file_md5(filename))
224    print(md5_result)
225    return md5_result
226
227def output_sha1(filename):
228    sha1_result = "%s  %s" % (filename, generate_file_sha1(filename))
229    print(sha1_result)
230    return sha1_result
231
232def output_sha256(filename):
233    sha256_result = "%s  %s" % (filename, generate_file_sha256(filename))
234    print(sha256_result)
235    return sha256_result
236
237# True if valid, False otherwise
238# Generalized validation function
239#   filename    - file to check
240#   chksum_file - checksum file to verify against
241#   hash_name   - name of hash function used
242#   hash_regex  - regex to validate the hash format
243#   hash_func   - function to create hash from file
244def validate_gen(filename, chksum_file, hash_name, hash_regex, hash_func):
245    print("      -> Validating file with %s: %s" % (hash_name, filename))
246    try:
247        hash_fh = open(chksum_file)
248        correct_hash = hash_fh.read().strip()
249        hash_fh.close()
250    except IOError:
251        print("      !! ERROR: Could not open checksum file '%s' for opening!" % chksum_file)
252        print("      !!        Exact error follows...")
253        raise
254
255    # Ensure hash is a valid checksum
256    hash_match = re.match(hash_regex, correct_hash)
257
258    if not hash_match:
259        print("      !! ERROR: Invalid %s checksum!" % hash_name)
260        print("      !!        Extracted %s (invalid): %s" % (hash_name, correct_hash))
261        sys.exit(1)
262
263    # One more thing - check to make sure the file exists!
264    try:
265        test_fh = open(filename, "rb")
266        test_fh.close()
267    except IOError:
268        print("      !! ERROR: Can't check %s checksum - could not open file!" % hash_name)
269        print("      !!        File: %s" % filename)
270        print("      !! Traceback follows:")
271        traceback.print_exc()
272        return False
273
274    # Alright, let's compute the checksum!
275    cur_hash = hash_func(filename)
276
277    # Check the checksum...
278    if cur_hash != correct_hash:
279        print("      !! ERROR: %s checksums do not match!" % hash_name)
280        print("      !!        File: %s" % filename)
281        print("      !!        Current %s: %s" % (hash_name, cur_hash))
282        print("      !!        Correct %s: %s" % (hash_name, correct_hash))
283        return False
284
285    # Otherwise, everything is good!
286    return True
287
288def validate(filename):
289    valid_md5 = validate_gen(filename, filename + ".md5", "MD5", r'^[0-9a-f]{32}$', generate_file_md5)
290
291    if valid_md5:
292        valid_sha1 = validate_gen(filename, filename + ".sha1", "SHA1", r'^[0-9a-f]{40}$', generate_file_sha1)
293
294        if valid_sha1:
295            # Special case: SHA256.
296            # Check its existence before attempting to validate.
297            if check_file(filename + ".sha256") or SHA256_STRICT:
298                valid_sha256 = validate_gen(filename, filename + ".sha256", "SHA256", r'^[0-9a-f]{64}$', generate_file_sha256)
299
300                return valid_sha256
301            else:
302                print("      !! **********************************************************")
303                print("      !! WARNING: SHA256 checksum was not found for file:")
304                print("         %s" % filename)
305                print("         SHA256 checksum is strongly suggested for file integrity")
306                print("         checking due to the weakness of other hashing algorithms.")
307                print("         Continuing for now.")
308                print("      !! **********************************************************")
309                return True
310        else:
311            return False # alternatively, valid_sha1
312    else:
313        return False # alternatively, valid_md5
314
315def dl_and_validate(url):
316    validation_attempts = 0
317    local_fn = os.path.basename(url)
318
319    print("   -> Downloading + validating:")
320    print("      %s" % truncate_url(url))
321
322    # Download checksums...
323    print("      -> Downloading checksums for file: %s" % (local_fn))
324    dlfile(url + ".md5")
325    dlfile(url + ".sha1")
326
327    # SHA256 support was recently added, so do some careful checking
328    # here.
329    if check_url(url + ".sha256"):
330        dlfile(url + ".sha256")
331    else:
332        # https://oss.jfrog.org/artifactory/oss-snapshot-local/org/github/alberthdev/cemu/appveyor-qt/Qt5.12.0_Rel_Static_Win32_DevDeploy.7z
333        # https://oss.jfrog.org/api/storage/oss-snapshot-local/org/github/alberthdev/cemu/appveyor-qt/Qt5.12.0_Rel_Static_Win32_DevDeploy.7z
334        url_parsed = urlparse(url)
335        if url_parsed.netloc == "oss.jfrog.org" and url_parsed.path.startswith("/artifactory"):
336            file_info_json_url = url.replace("://oss.jfrog.org/artifactory/", "://oss.jfrog.org/api/storage/")
337
338            if check_url(file_info_json_url):
339                dlfile(file_info_json_url, local_fn + ".info.json")
340
341                file_info_json_fh = open(local_fn + ".info.json")
342                file_info_json = json.loads(file_info_json_fh.read())
343                file_info_json_fh.close()
344
345                if "checksums" in file_info_json and "sha256" in file_info_json["checksums"]:
346                    print("      -- Found SHA256 checksum from file JSON info.")
347                    print("         SHA256: %s" % file_info_json["checksums"]["sha256"])
348                    sha256_fh = open(local_fn + ".sha256", "w")
349                    sha256_fh.write("%s" % file_info_json["checksums"]["sha256"])
350                    sha256_fh.close()
351                else:
352                    print("      !! Could not find SHA256 checksum in JSON info.")
353            else:
354                print("      !! JSON info does not seem to work or exist:")
355                print("         %s" % file_info_json_url)
356                print("         Will not be able to locate SHA256 checksum.")
357        else:
358            print("      !! Could not detect a OSS JFrog URL. Will not be able")
359            print("         to find SHA256 checksum.")
360
361    while validation_attempts < MAX_ATTEMPTS:
362        # If we aren't on our first download attempt, wait a bit.
363        if validation_attempts > 0:
364            print("      !! Download + validation attempt failed, waiting 10s before retry...")
365            print("         (attempt %i/%i)" % (validation_attempts + 1, MAX_ATTEMPTS))
366            # Wait...
367            time.sleep(10)
368
369        print("      -> Downloading file: %s" % (local_fn))
370
371        # Download file...
372        dlfile(url)
373
374        # ...and attempt to validate it!
375        if validate(local_fn):
376            break
377
378        # Validation failed... looping back around.
379        # Increment validation attempt counter
380        validation_attempts += 1
381
382    if validation_attempts > MAX_ATTEMPTS:
383        print("      !! ERROR: Download and validation failed, exiting!")
384        sys.exit(1)
385
386    print("      -> Downloaded + validated successfully:")
387    print("         %s" % truncate_url(url))
388
389def extract(filename):
390    print("   -> Extracting file: %s" % filename)
391    if not silent_exec(["7z", "x", "-y", "-oC:\\", filename]):
392        print("   !! ERROR: Failed to extract file: " % filename)
393        print("   !!        See above output for details.")
394        sys.exit(1)
395
396def install_deps():
397    print(" * Attempting to download dependencies...")
398    dl_and_validate('https://oss.jfrog.org/artifactory/oss-snapshot-local/org/github/alberthdev/cemu/appveyor-qt/Qt5.12.0_Rel_Static_Win32_DevDeploy.7z.001')
399    dl_and_validate('https://oss.jfrog.org/artifactory/oss-snapshot-local/org/github/alberthdev/cemu/appveyor-qt/Qt5.12.0_Rel_Static_Win32_DevDeploy.7z.002')
400    dl_and_validate('https://oss.jfrog.org/artifactory/oss-snapshot-local/org/github/alberthdev/cemu/appveyor-qt/Qt5.12.0_Rel_Static_Win64_DevDeploy.7z.001')
401    dl_and_validate('https://oss.jfrog.org/artifactory/oss-snapshot-local/org/github/alberthdev/cemu/appveyor-qt/Qt5.12.0_Rel_Static_Win64_DevDeploy.7z.002')
402
403    print(" * Attempting to install dependencies...")
404    extract('Qt5.12.0_Rel_Static_Win32_DevDeploy.7z.001')
405    extract('Qt5.12.0_Rel_Static_Win64_DevDeploy.7z.001')
406
407    print(" * Successfully installed build dependencies!")
408
409def overwrite_copy(src, dest):
410    src_bn = os.path.basename(src)
411    dest_path = os.path.join(dest, src_bn)
412
413    src_fh = open(src, "rb")
414    dest_fh = open(dest_path, "wb")
415
416    dest_fh.write(src_fh.read())
417
418    src_fh.close()
419    dest_fh.close()
420
421def collect_static_main_files(arch, build_path, dest, extra_wc = None):
422    file_list = []
423
424    if extra_wc:
425        for copy_type in extra_wc:
426            print("   -> Copying %s files (%s)..." % (copy_type, arch))
427            copy_wc = extra_wc[copy_type]
428
429            for file in glob.glob(copy_wc):
430                print("      -> Copying %s (%s, %s)..." % (os.path.basename(file), arch, copy_type))
431                overwrite_copy(file, dest)
432
433
434    # Finally, add our binary!
435    print("   -> Copying main executable (%s)..." % (arch))
436    exec_path = os.path.join(build_path, "CEmu.exe")
437    overwrite_copy(exec_path, dest)
438
439    # No manifest needed - already embedded into exe.
440
441# wc = wildcard
442# extra_wc: dictionary of extra wildcard paths to copy in!
443#   example: { "More DLLs" : "C:\MoreDLLs\*.dll"}
444def collect_main_files(arch, vcredist_wc_path, ucrt_wc_path, build_path, dest, extra_wc = None):
445    file_list = []
446
447    print("   -> Searching VCRedist for DLL files to include (%s)..." % (arch))
448
449    for file in glob.glob(vcredist_wc_path):
450        print("   -> Copying %s (%s, VCRedist)..." % (os.path.basename(file), arch))
451        overwrite_copy(file, dest)
452
453    for file in glob.glob(ucrt_wc_path):
454        print("   -> Copying %s (%s, UCRT)..." % (os.path.basename(file), arch))
455        overwrite_copy(file, dest)
456
457    if extra_wc:
458        for copy_type in extra_wc:
459            print("   -> Copying %s files (%s)..." % (copy_type, arch))
460            copy_wc = extra_wc[copy_type]
461
462            for file in glob.glob(copy_wc):
463                print("      -> Copying %s (%s, %s)..." % (os.path.basename(file), arch, copy_type))
464                overwrite_copy(file, dest)
465
466
467    # Finally, add our binary!
468    print("   -> Copying main executable (%s)..." % (arch))
469    exec_path = os.path.join(build_path, "CEmu.exe")
470    overwrite_copy(exec_path, dest)
471
472    # No manifest needed - already embedded into exe.
473
474def collect_qt_files_with_qml(arch, deploy_tool, dest, exe_file, qmldir = "qml"):
475    os.environ.pop("VCINSTALLDIR", None)
476    print("   -> Collecting all Qt dependencies (%s)..." % (arch))
477    if not simple_exec([deploy_tool, "--qmldir", qmldir, "--dir", dest, exe_file]):
478        print("   !! ERROR: Failed to collect Qt dependencies!")
479        print("   !!        See above output for details.")
480        sys.exit(1)
481
482def collect_qt_files(arch, deploy_tool, dest, exe_file):
483    os.environ.pop("VCINSTALLDIR", None)
484    print("   -> Collecting all Qt dependencies (%s)..." % (arch))
485    if not simple_exec([deploy_tool, "--no-angle", "--no-opengl-sw", "--no-system-d3d-compiler", "--dir", dest, exe_file]):
486        print("   !! ERROR: Failed to collect Qt dependencies!")
487        print("   !!        See above output for details.")
488        sys.exit(1)
489
490def build_file_list(arch, dest):
491    file_list = []
492    root_parts = len(dest.split(os.sep))
493
494    print("   -> Finalizing file list for release (%s)..." % (arch))
495
496    for root, dirnames, filenames in os.walk(dest):
497        for filename in filenames:
498            full_path = os.path.join(root, filename)
499            # Delete root folder from path to form archive path!
500            arc_path = os.sep.join(full_path.split(os.sep)[root_parts:])
501            file_list.append([full_path, arc_path])
502
503    return file_list
504
505def make_zip(arch, filename, file_list):
506    print(" * Building ZIP file %s (%s)..." % (filename, arch))
507
508    if compression == zipfile.ZIP_DEFLATED:
509        print("   (Compression is enabled!)")
510    else:
511        print("   (Compression is DISABLED!)")
512
513    zf = zipfile.ZipFile(filename, mode='w')
514    try:
515        for file_entry in file_list:
516            if len(file_entry) == 1:
517                full_path = file_entry[0]
518                arc_path = full_path
519            elif len(file_entry) == 2:
520                full_path = file_entry[0]
521                arc_path = file_entry[1]
522            else:
523                print("   !! ERROR: Bug - invalid number of file elements in file_entry!")
524                print("             file_entry is %s" % (str(file_entry)))
525                sys.exit(1)
526
527            print("   -> Adding %s -> %s..." % (full_path, arc_path))
528            zf.write(full_path, compress_type=compression, arcname=arc_path)
529    finally:
530        print("   -> Closing ZIP file %s (%s)..." % (filename, arch))
531        zf.close()
532
533    print(" * Successfully built ZIP file %s (%s)!" % (filename, arch))
534
535def upload_snapshot(filename, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key, extra_path = None):
536    cur_date = cur_timestamp.split("_")[0]
537    cur_date_ym = cur_date[:6]
538
539    full_path = BINTRAY_SNAPSHOT_SERVER_PATH + BINTRAY_MAVEN_GROUP_PATH + "git/" + cur_date_ym + "/" + cur_date + "/" + snap_base_fn + "/"
540
541    base_fn = os.path.basename(filename)
542
543    print(" * Preparing to deploy snapshot: %s" % base_fn)
544
545    if extra_path:
546        full_path += extra_path
547
548    # Compute MD5, SHA1, and SHA256
549    print("   -> Computing checksums before uploading...")
550    file_md5sum = generate_file_md5(filename)
551    file_sha1sum = generate_file_sha1(filename)
552    file_sha256sum = generate_file_sha256(filename)
553
554    print("   -> MD5    = %s" % file_md5sum)
555    print("   -> SHA1   = %s" % file_sha1sum)
556    print("   -> SHA256 = %s" % file_sha256sum)
557
558    headers = {
559                'X-Checksum-Md5'    : file_md5sum,
560                'X-Checksum-Sha1'   : file_sha1sum,
561                'X-Checksum-Sha256' : file_sha256sum,
562              }
563
564    #files = {base_fn: open(filename, 'rb')}
565    fh = open(filename, 'rb')
566    file_data = fh.read()
567    fh.close()
568
569    print(" * Uploading/deploying snapshot: %s" % base_fn)
570    r = requests.put(full_path + base_fn, headers = headers, data = file_data, \
571                    auth = (bintray_api_username, bintray_api_key))
572
573    print(" * Resulting status code: %i" % r.status_code)
574    print(" * Resulting response:\n%s" % r.content)
575
576    if r.status_code != 201:
577        print(" ! ERROR: Upload/deployment of snapshot failed!")
578
579def deploy_snapshots():
580    print(" * Preparing to deploy...")
581
582    # Check for our needed environment variables!
583    # First up? Bintray!
584    bintray_api_username = os.environ.get("BINTRAY_API_USERNAME")
585    bintray_api_key = os.environ.get("BINTRAY_API_KEY")
586
587    if (bintray_api_username == None) or (bintray_api_key == None):
588        print(" ! ERROR: Authentication environmental variables not found!")
589        print(" !        BINTRAY_API_USERNAME defined? %s" % ("Yes" if bintray_api_username else "No"))
590        print(" !        BINTRAY_API_KEY defined?      %s" % ("Yes" if bintray_api_key else "No"))
591        sys.exit(1)
592
593    # One more - are the Qt5 dynamic directories defined?
594    qt5_bin_dir_dynamic_32 = os.environ.get("QT5_BIN_DIR_DYNAMIC_32")
595    qt5_bin_dir_dynamic_64 = os.environ.get("QT5_BIN_DIR_DYNAMIC_64")
596
597    if (qt5_bin_dir_dynamic_32 == None) or (qt5_bin_dir_dynamic_64 == None):
598        print(" ! ERROR: Qt5 dynamic location environmental variables not found!")
599        print(" !        QT5_BIN_DIR_DYNAMIC_32 defined? %s" % ("Yes" if QT5_BIN_DIR_DYNAMIC_32 else "No"))
600        print(" !        QT5_BIN_DIR_DYNAMIC_64 defined? %s" % ("Yes" if QT5_BIN_DIR_DYNAMIC_64 else "No"))
601        sys.exit(1)
602
603    # Make a directory for our deploy ZIPs
604    mkdir_p("deploy")
605    mkdir_p(os.path.join("deploy", "release32"))
606    mkdir_p(os.path.join("deploy", "release64"))
607    mkdir_p(os.path.join("deploy", "release32_debug"))
608    mkdir_p(os.path.join("deploy", "release64_debug"))
609
610    mkdir_p(os.path.join("deploy_static", "release32"))
611    mkdir_p(os.path.join("deploy_static", "release64"))
612    mkdir_p(os.path.join("deploy_static", "release32_debug"))
613    mkdir_p(os.path.join("deploy_static", "release64_debug"))
614
615    # git rev-parse --short HEAD
616    git_rev = output_exec(["git", "rev-parse", "--short", "HEAD"])
617
618    if git_rev == None:
619        sys.exit(1)
620
621    git_rev = git_rev.decode("utf-8").strip()
622
623    # Snapshot filename - based on http://zeranoe1.rssing.com/chan-5973786/latest.php
624    cur_timestamp = time.strftime("%Y%m%d_%H%M%S")
625    snap_base_fn = "cemu-%s-git-%s" % (cur_timestamp, git_rev)
626    snap_base_path = os.path.join("deploy", snap_base_fn)
627
628    # Locate files that we need!
629    print(" * Collecting all dependencies for deployment...")
630
631    collect_main_files("x86", r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x86\Microsoft.VC140.CRT\*.dll",
632                       r"C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86\*.dll",
633                       os.path.join("build_32", "release"),
634                       os.path.join("deploy", "release32"),
635                       extra_wc = {
636                                    "vcpkg provided DLLs" : os.path.join("build_32", "release") + r"\*.dll"
637                                  }
638                      )
639    collect_main_files("x64", r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\x64\Microsoft.VC140.CRT\*.dll",
640                       r"C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x64\*.dll",
641                       os.path.join("build_64", "release"),
642                       os.path.join("deploy", "release64"),
643                       extra_wc = {
644                                    "vcpkg provided DLLs" : os.path.join("build_64", "release") + r"\*.dll"
645                                  }
646                      )
647
648    # For debug builds, only copy api*.dll for UCRT redist, then copy
649    # the specific ucrt debug DLL in the extra copy arg.
650    collect_main_files("x86 Debug", r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\debug_nonredist\x86\Microsoft.VC140.DebugCRT\*.dll",
651                       r"C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x86\api*.dll",
652                       os.path.join("build_32", "debug"),
653                       os.path.join("deploy", "release32_debug"),
654                       extra_wc = {
655                                    "UCRT Debug" : r"C:\Program Files (x86)\Windows Kits\10\bin\x86\ucrt\*.dll",
656                                    "vcpkg provided DLLs" : os.path.join("build_32", "debug") + r"\*.dll"
657                                  }
658                      )
659    collect_main_files("x64 Debug", r"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\redist\debug_nonredist\x64\Microsoft.VC140.DebugCRT\*.dll",
660                       r"C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x64\api*.dll",
661                       os.path.join("build_64", "debug"),
662                       os.path.join("deploy", "release64_debug"),
663                       extra_wc = {
664                                    "UCRT Debug" : r"C:\Program Files (x86)\Windows Kits\10\bin\x64\ucrt\*.dll",
665                                    "vcpkg provided DLLs" : os.path.join("build_64", "debug") + r"\*.dll"
666                                  }
667                      )
668
669    # Static builds
670    # Release
671    collect_static_main_files("x86 Static", os.path.join("build_static_32", "release"), os.path.join("deploy_static", "release32"),
672                       #extra_wc = {
673                       #             "EGL Library" : r"C:\Qt\Qt5.12.0-static\bin\libEGL.dll",
674                       #             "GLESv2 Library" : r"C:\Qt\Qt5.12.0-static\bin\libGLESv2.dll",
675                       #             "DirectX Compiler Library" : r"C:\Qt\5.12.0\msvc2015\bin\d3dcompiler_*.dll",
676                       #             "Mesa OpenGL Software Rendering Library" : r"C:\Qt\5.12.0\msvc2015\bin\opengl32sw.dll",
677                       #           }
678                      )
679    collect_static_main_files("x64 Static", os.path.join("build_static_64", "release"), os.path.join("deploy_static", "release64"),
680                       #extra_wc = {
681                       #             "EGL Library" : r"C:\Qt\Qt5.12.0x64-static\bin\libEGL.dll",
682                       #             "GLESv2 Library" : r"C:\Qt\Qt5.12.0x64-static\bin\libGLESv2.dll",
683                       #             "DirectX Compiler Library" : r"C:\Qt\5.12.0\msvc2015_64\bin\d3dcompiler_*.dll",
684                       #             "Mesa OpenGL Software Rendering Library" : r"C:\Qt\5.12.0\msvc2015_64\bin\opengl32sw.dll",
685                       #           }
686                      )
687
688    # Debug
689    collect_static_main_files("x86 Static Debug", os.path.join("build_static_32", "debug"), os.path.join("deploy_static", "release32_debug"),
690                       #extra_wc = {
691                       #             "EGL Library" : r"C:\Qt\Qt5.12.0-static\bin\libEGLd.dll",
692                       #             "GLESv2 Library" : r"C:\Qt\Qt5.12.0-static\bin\libGLESv2d.dll",
693                       #             "DirectX Compiler Library" : r"C:\Qt\5.12.0\msvc2015\bin\d3dcompiler_*.dll",
694                       #             "Mesa OpenGL Software Rendering Library" : r"C:\Qt\5.12.0\msvc2015\bin\opengl32sw.dll",
695                       #           }
696                      )
697    collect_static_main_files("x64 Static Debug", os.path.join("build_static_64", "debug"), os.path.join("deploy_static", "release64_debug"),
698                       #extra_wc = {
699                       #             "EGL Library" : r"C:\Qt\Qt5.12.0x64-static\bin\libEGLd.dll",
700                       #             "GLESv2 Library" : r"C:\Qt\Qt5.12.0x64-static\bin\libGLESv2d.dll",
701                       #             "DirectX Compiler Library" : r"C:\Qt\5.12.0\msvc2015_64\bin\d3dcompiler_*.dll",
702                       #             "Mesa OpenGL Software Rendering Library" : r"C:\Qt\5.12.0\msvc2015_64\bin\opengl32sw.dll",
703                       #           }
704                      )
705
706    # Qt files only needed for dynamic builds
707    collect_qt_files("x86", qt5_bin_dir_dynamic_32 + r"\windeployqt.exe", r"deploy\release32", r'build_32\release\CEmu.exe')
708    collect_qt_files("x64", qt5_bin_dir_dynamic_64 + r"\windeployqt.exe", r"deploy\release64", r'build_64\release\CEmu.exe')
709
710    collect_qt_files("x86 Debug", qt5_bin_dir_dynamic_32 + r"\windeployqt.exe", r"deploy\release32_debug", r'build_32\debug\CEmu.exe')
711    collect_qt_files("x64 Debug", qt5_bin_dir_dynamic_64 + r"\windeployqt.exe", r"deploy\release64_debug", r'build_64\debug\CEmu.exe')
712
713    file_list_32 = build_file_list("x86", r"deploy\release32")
714    file_list_64 = build_file_list("x64", r"deploy\release64")
715
716    file_list_32_debug = build_file_list("x86 Debug", r"deploy\release32_debug")
717    file_list_64_debug = build_file_list("x64 Debug", r"deploy\release64_debug")
718
719    file_list_32_static = build_file_list("x86", r"deploy_static\release32")
720    file_list_64_static = build_file_list("x64", r"deploy_static\release64")
721
722    file_list_32_static_debug = build_file_list("x86 Debug", r"deploy_static\release32_debug")
723    file_list_64_static_debug = build_file_list("x64 Debug", r"deploy_static\release64_debug")
724
725    # Build our ZIPs!
726    cemu_win32_zip_fn = snap_base_path + "-win32-release-shared.zip"
727    cemu_win64_zip_fn = snap_base_path + "-win64-release-shared.zip"
728
729    cemu_win32_debug_zip_fn = snap_base_path + "-win32-debug-shared.zip"
730    cemu_win64_debug_zip_fn = snap_base_path + "-win64-debug-shared.zip"
731
732    cemu_win32_static_zip_fn = snap_base_path + "-win32-release-static.zip"
733    cemu_win64_static_zip_fn = snap_base_path + "-win64-release-static.zip"
734
735    cemu_win32_static_debug_zip_fn = snap_base_path + "-win32-debug-static.zip"
736    cemu_win64_static_debug_zip_fn = snap_base_path + "-win64-debug-static.zip"
737
738    make_zip("x86", cemu_win32_zip_fn, file_list_32)
739    make_zip("x64", cemu_win64_zip_fn, file_list_64)
740
741    make_zip("x86 Debug", cemu_win32_debug_zip_fn, file_list_32_debug)
742    make_zip("x64 Debug", cemu_win64_debug_zip_fn, file_list_64_debug)
743
744    make_zip("x86 Static", cemu_win32_static_zip_fn, file_list_32_static)
745    make_zip("x64 Static", cemu_win64_static_zip_fn, file_list_64_static)
746
747    make_zip("x86 Static Debug", cemu_win32_static_debug_zip_fn, file_list_32_static_debug)
748    make_zip("x64 Static Debug", cemu_win64_static_debug_zip_fn, file_list_64_static_debug)
749
750    # Upload everything!
751    upload_snapshot(cemu_win32_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
752    upload_snapshot(cemu_win64_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
753
754    upload_snapshot(cemu_win32_debug_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
755    upload_snapshot(cemu_win64_debug_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
756
757    upload_snapshot(cemu_win32_static_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
758    upload_snapshot(cemu_win64_static_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
759
760    upload_snapshot(cemu_win32_static_debug_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
761    upload_snapshot(cemu_win64_static_debug_zip_fn, cur_timestamp, snap_base_fn, bintray_api_username, bintray_api_key)
762
763    print(" * Snapshot deployment complete!")
764
765def usage(msg = None):
766    if msg:
767        print(msg)
768
769    print("Usage: %s [make_checksum|install|deploy]" % sys.argv[0])
770    sys.exit(1)
771
772if __name__ == "__main__":
773    if len(sys.argv) != 2:
774        usage()
775        sys.exit(1)
776
777    if sys.argv[1] == "make_checksum":
778        make_checksum()
779    elif sys.argv[1] == "install":
780        install_deps()
781    elif sys.argv[1] == "deploy":
782        deploy_snapshots()
783    else:
784        usage("ERROR: Invalid command!")
785