1# ##### BEGIN GPL LICENSE BLOCK #####
2#
3#  This program is free software; you can redistribute it and/or
4#  modify it under the terms of the GNU General Public License
5#  as published by the Free Software Foundation; either version 2
6#  of the License, or (at your option) any later version.
7#
8#  This program is distributed in the hope that it will be useful,
9#  but WITHOUT ANY WARRANTY; without even the implied warranty of
10#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11#  GNU General Public License for more details.
12#
13#  You should have received a copy of the GNU General Public License
14#  along with this program; if not, write to the Free Software Foundation,
15#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16#
17# ##### END GPL LICENSE BLOCK #####
18
19# <pep8 compliant>
20
21import argparse
22import os
23import re
24import subprocess
25import sys
26
27
28def is_tool(name):
29    """Check whether `name` is on PATH and marked as executable."""
30
31    # from whichcraft import which
32    from shutil import which
33
34    return which(name) is not None
35
36
37class Builder:
38    def __init__(self, name, branch, codesign):
39        self.name = name
40        self.branch = branch
41        self.is_release_branch = re.match("^blender-v(.*)-release$", branch) is not None
42        self.codesign = codesign
43
44        # Buildbot runs from build/ directory
45        self.blender_dir = os.path.abspath(os.path.join('..', 'blender.git'))
46        self.build_dir = os.path.abspath(os.path.join('..', 'build'))
47        self.install_dir = os.path.abspath(os.path.join('..', 'install'))
48        self.upload_dir = os.path.abspath(os.path.join('..', 'install'))
49
50        # Detect platform
51        if name.startswith('mac'):
52            self.platform = 'mac'
53            self.command_prefix = []
54        elif name.startswith('linux'):
55            self.platform = 'linux'
56            if is_tool('scl'):
57                self.command_prefix = ['scl', 'enable', 'devtoolset-9', '--']
58            else:
59                self.command_prefix = []
60        elif name.startswith('win'):
61            self.platform = 'win'
62            self.command_prefix = []
63        else:
64            raise ValueError('Unkonw platform for builder ' + self.platform)
65
66        # Always 64 bit now
67        self.bits = 64
68
69
70def create_builder_from_arguments():
71    parser = argparse.ArgumentParser()
72    parser.add_argument('builder_name')
73    parser.add_argument('branch', default='master', nargs='?')
74    parser.add_argument("--codesign", action="store_true")
75    args = parser.parse_args()
76    return Builder(args.builder_name, args.branch, args.codesign)
77
78
79class VersionInfo:
80    def __init__(self, builder):
81        # Get version information
82        buildinfo_h = os.path.join(builder.build_dir, "source", "creator", "buildinfo.h")
83        blender_h = os.path.join(builder.blender_dir, "source", "blender", "blenkernel", "BKE_blender_version.h")
84
85        version_number = int(self._parse_header_file(blender_h, 'BLENDER_VERSION'))
86        version_number_patch = int(self._parse_header_file(blender_h, 'BLENDER_VERSION_PATCH'))
87        version_numbers = (version_number // 100, version_number % 100, version_number_patch)
88        self.short_version = "%d.%02d" % (version_numbers[0], version_numbers[1])
89        self.version = "%d.%02d.%d" % version_numbers
90        self.version_cycle = self._parse_header_file(blender_h, 'BLENDER_VERSION_CYCLE')
91        self.version_cycle_number = self._parse_header_file(blender_h, 'BLENDER_VERSION_CYCLE_NUMBER')
92        self.hash = self._parse_header_file(buildinfo_h, 'BUILD_HASH')[1:-1]
93
94        if self.version_cycle == "release":
95            # Final release
96            self.full_version = self.version
97            self.is_development_build = False
98        elif self.version_cycle == "rc":
99            # Release candidate
100            version_cycle = self.version_cycle + self.version_cycle_number
101            self.full_version = self.version + version_cycle
102            self.is_development_build = False
103        else:
104            # Development build
105            self.full_version = self.version + '-' + self.hash
106            self.is_development_build = True
107
108    def _parse_header_file(self, filename, define):
109        import re
110        regex = re.compile(r"^#\s*define\s+%s\s+(.*)" % define)
111        with open(filename, "r") as file:
112            for l in file:
113                match = regex.match(l)
114                if match:
115                    return match.group(1)
116        return None
117
118
119def call(cmd, env=None, exit_on_error=True):
120    print(' '.join(cmd))
121
122    # Flush to ensure correct order output on Windows.
123    sys.stdout.flush()
124    sys.stderr.flush()
125
126    retcode = subprocess.call(cmd, env=env)
127    if exit_on_error and retcode != 0:
128        sys.exit(retcode)
129    return retcode
130