1import os, os.path, shutil, sys, subprocess
2from .utils import *
3from .config import *
4
5class Batch(object):
6    def __init__(self, bconf):
7        self.bconf = bconf
8        self.commands = []
9
10        self.add(self.vcvars_cmd)
11        self.add('echo on')
12        if self.bconf.vc_version == 'vc14':
13            # I don't know why vcvars doesn't configure this under vc14
14            self.add('set include=%s\\include;%%include%%' % self.bconf.windows_sdk_path)
15            if self.bconf.bitness == 32:
16                self.add('set lib=%s\\lib;%%lib%%' % self.bconf.windows_sdk_path)
17                self.add('set path=%s\\bin;%%path%%' % self.bconf.windows_sdk_path)
18            else:
19                self.add('set lib=%s\\lib\\x64;%%lib%%' % self.bconf.windows_sdk_path)
20                self.add('set path=%s\\bin\\x64;%%path%%' % self.bconf.windows_sdk_path)
21        self.add(self.nasm_cmd)
22
23        self.add('set path=%s;%%path%%' % self.bconf.extra_bin_paths[self.bconf.bitness]['rc_bin'])
24
25    def add(self, cmd):
26        self.commands.append(cmd)
27
28    # if patch fails to apply hunks, it exits with nonzero code.
29    # if patch doesn't find the patch file to apply, it exits with a zero code!
30    ERROR_CHECK = 'IF %ERRORLEVEL% NEQ 0 exit %errorlevel%'
31
32    def batch_text(self):
33        return ("\n" + self.ERROR_CHECK + "\n").join(self.commands)
34
35    @property
36    def vcvars_bitness_parameter(self):
37        params = {
38            32: 'x86',
39            64: 'amd64',
40        }
41        return params[self.bconf.bitness]
42
43    @property
44    def vcvars_relative_path(self):
45        return 'vc/vcvarsall.bat'
46
47    @property
48    def vc_path(self):
49        if self.bconf.vc_version in self.bconf.vc_paths and self.bconf.vc_paths[self.bconf.vc_version]:
50            path = self.bconf.vc_paths[self.bconf.vc_version]
51            if not os.path.join(path, self.vcvars_relative_path):
52                raise Exception('vcvars not found in specified path')
53            return path
54        else:
55            for path in self.bconf.default_vc_paths[self.bconf.vc_version]:
56                if os.path.exists(os.path.join(path, self.vcvars_relative_path)):
57                    return path
58            raise Exception('No usable vc path found')
59
60    @property
61    def vcvars_path(self):
62        return os.path.join(self.vc_path, self.vcvars_relative_path)
63
64    @property
65    def vcvars_cmd(self):
66        # https://msdn.microsoft.com/en-us/library/x4d2c09s.aspx
67        return "call \"%s\" %s" % (
68            self.vcvars_path,
69            self.vcvars_bitness_parameter,
70        )
71
72    @property
73    def nasm_cmd(self):
74        return "set path=%s;%%path%%\n" % self.bconf.nasm_path
75
76class Builder(object):
77    def __init__(self, **kwargs):
78        self.bconf = kwargs.pop('bconf')
79        self.use_dlls = False
80
81    @contextlib.contextmanager
82    def execute_batch(self):
83        batch = Batch(self.bconf)
84        yield batch
85        with open('doit.bat', 'w') as f:
86            f.write(batch.batch_text())
87        if False:
88            print("Executing:")
89            with open('doit.bat', 'r') as f:
90                print(f.read())
91            sys.stdout.flush()
92        rv = subprocess.call(['doit.bat'])
93        if rv != 0:
94            print("\nFailed to execute the following commands:\n")
95            with open('doit.bat', 'r') as f:
96                print(f.read())
97            sys.stdout.flush()
98            exit(3)
99
100class StandardBuilder(Builder):
101    @property
102    def state_tag(self):
103        return self.output_dir_path
104
105    @property
106    def bin_path(self):
107        return os.path.join(self.bconf.archives_path, self.output_dir_path, 'dist', 'bin')
108
109    @property
110    def include_path(self):
111        return os.path.join(self.bconf.archives_path, self.output_dir_path, 'dist', 'include')
112
113    @property
114    def lib_path(self):
115        return os.path.join(self.bconf.archives_path, self.output_dir_path, 'dist', 'lib')
116
117    @property
118    def dll_paths(self):
119        raise NotImplementedError
120
121    @property
122    def builder_name(self):
123        return self.__class__.__name__.replace('Builder', '').lower()
124
125    @property
126    def my_version(self):
127        return getattr(self.bconf, '%s_version' % self.builder_name)
128
129    @property
130    def output_dir_path(self):
131        return '%s-%s-%s' % (self.builder_name, self.my_version, self.bconf.vc_tag)
132
133    def standard_fetch_extract(self, url_template):
134        url = url_template % dict(
135            my_version=self.my_version,
136        )
137        fetch(url)
138        archive_basename = os.path.basename(url)
139        archive_name = archive_basename.replace('.tar.gz', '')
140        untar(self.bconf, archive_name)
141
142        suffixed_dir = self.output_dir_path
143        if os.path.exists(suffixed_dir):
144            shutil.rmtree(suffixed_dir)
145        os.rename(archive_name, suffixed_dir)
146        return suffixed_dir
147