Remove 291
[blender-buildbot.git] / master_unpack.py
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 # Runs on Buildbot master, to unpack incoming unload.zip into latest
20 # builds directory and remove older builds.
21
22 # <pep8 compliant>
23
24 import argparse
25 import os
26 import shutil
27 import sys
28 import time
29 import zipfile
30
31 DOWNLOAD_DIR = "/data/www/vhosts/builder.blender.org/webroot/download/"
32
33 # Parse package filename to extract branch and platform
34 class Package:
35     def __init__(self, zipname):
36         self.zipname = zipname
37         self.filename = os.path.basename(zipname)
38         self.version = ""
39         self.branch = ""
40         self.platform = ""
41         self.parsed = False
42         self.parse_filename()
43
44     # extension stripping
45     def _strip_extension(self, filename):
46         filename, _ = os.path.splitext(filename)
47         if filename.endswith('.tar'):
48             filename, _ = os.path.splitext(filename)
49
50         return filename
51
52     # extract platform from package name
53     def parse_filename(self):
54         # Name is one one of:
55         # blender-version-platform: official release
56         # blender-version-hash-platform: daily build
57         # branch-blender-version-hash-platform: branch build
58         filename = self._strip_extension(self.filename)
59         tokens = filename.split("-")
60
61         if len(tokens) < 3:
62             return
63         if "blender" not in tokens:
64             return
65
66         blender_index = tokens.index("blender")
67         if not (blender_index + 2 < len(tokens)):
68             return
69
70         # Detect branch.
71         branch = "-".join(tokens[:blender_index])
72
73         # Detect version.
74         if branch == "":
75             version = tokens[blender_index + 1]
76             if len(tokens) == 3:
77                 version += " release"
78         else:
79             version = ""
80
81         # Detect platform.
82         platform = tokens[-1]
83         platforms = ('osx', 'mac', 'bsd',
84                      'win', 'linux', 'source',
85                      'irix', 'solaris', 'mingw')
86         platform_found = False
87         for name in platforms:
88             if platform.lower().startswith(name):
89                 platform_found = True
90         if not platform_found:
91             return
92
93         self.version = version
94         self.platform = platform
95         self.branch = branch
96         self.parsed = True
97
98 def get_download_dir(branch):
99     if not branch or branch == 'master':
100         directory = DOWNLOAD_DIR
101     else:
102         directory = os.path.join(DOWNLOAD_DIR, branch)
103         os.makedirs(directory, exist_ok=True)
104         os.chmod(directory, 0o755)
105
106     return directory
107
108 def open_zipfile(filename):
109     # Open zip file
110     if not os.path.exists(filename):
111         sys.stderr.write("File %r not found.\n" % filename)
112         sys.exit(1)
113
114     try:
115         return zipfile.ZipFile(filename, "r")
116     except Exception as ex:
117         sys.stderr.write('Failed to open zip file: %s\n' % str(ex))
118         sys.exit(1)
119
120 def get_zipfile_packages(zipfile):
121     # List packages in zip file
122     packages = [Package(zipname) for zipname in zipfile.namelist()]
123     for package in packages:
124         if not package.parsed:
125             sys.stderr.write("Failed to parse package filename: %s\n" % str(package.filename))
126             sys.exit(1)
127     if len(packages) == 0:
128         sys.stderr.write('Empty zip file\n')
129         sys.exit(1)
130     return packages
131
132 def get_branch_platform(packages):
133     # Extract branch and platform names
134     branch = packages[0].branch
135     platform = packages[0].platform
136     version = packages[0].version
137
138     if platform == '':
139         sys.stderr.write('Failed to detect platform ' +
140             'from package: %r\n' % packages[0].filename)
141         sys.exit(1)
142
143     for package in packages:
144         if package.branch != branch or package.platform != platform or package.version != version:
145             sys.stderr.write('All packages in the zip file must have the same branch and platform\n')
146             sys.exit(1)
147
148     return branch, platform, version
149
150 def extract_zipfile_packages(zipfile, packages, branch):
151     # Extract packages from zip file
152     directory = get_download_dir(branch)
153
154     for package in packages:
155         filepath = os.path.join(directory, package.filename)
156
157         try:
158             zf = zipfile.open(package.zipname)
159             f = open(filepath, "wb")
160
161             shutil.copyfileobj(zf, f)
162             os.chmod(filepath, 0o644)
163
164             zf.close()
165         except Exception as ex:
166             sys.stderr.write('Failed to unzip package: %s\n' % str(ex))
167             sys.exit(1)
168
169 def remove_replaced_packages(branch, platform, version, new_packages):
170     # Remove other files from the same platform and branch that are replaced
171     # by the new packages.
172     directory = get_download_dir(branch)
173
174     for filename in os.listdir(directory):
175         package = Package(filename)
176         if package.platform == platform and package.branch == branch and package.version == version:
177             is_new_package = False
178             for new_package in new_packages:
179                 if package.filename == new_package.filename:
180                     is_new_package = True
181
182             if not is_new_package:
183                 try:
184                     print("Removing older package version", filename)
185                     os.remove(os.path.join(directory, filename))
186                 except Exception as ex:
187                     sys.stderr.write('Failed to remove replaced package: %s\n' % str(ex))
188
189 def remove_old_packages():
190     # Remove any branch packages that are 100 days or older.
191     cutoff_time = time.time() - 100 * 86400
192
193     for dirname in os.listdir(DOWNLOAD_DIR):
194         dirpath = os.path.join(DOWNLOAD_DIR, dirname)
195         if not os.path.isdir(dirpath):
196             continue
197
198         for filename in os.listdir(dirpath):
199             filepath = os.path.join(dirpath, filename)
200             if not os.path.isfile(filepath):
201                 continue
202
203             file_mtime = os.stat(filepath).st_mtime
204             if file_mtime < cutoff_time:
205                 try:
206                     print("Removing old branch build", filename)
207                     os.remove(filepath)
208                 except Exception as ex:
209                     sys.stderr.write('Failed to remove old package: %s\n' % str(ex))
210
211 if __name__ == "__main__":
212     parser = argparse.ArgumentParser()
213     parser.add_argument('filename')
214     args = parser.parse_args()
215
216     with open_zipfile(args.filename) as zipfile:
217         packages = get_zipfile_packages(zipfile)
218         branch, platform, version = get_branch_platform(packages)
219         extract_zipfile_packages(zipfile, packages, branch)
220         remove_replaced_packages(branch, platform, version, packages)
221
222     remove_old_packages()