Add tool to check licence in files
[archipelago] / devtools / check_licence.py
1 #!/usr/bin/env python
2
3 # Copyright 2013 GRNET S.A. All rights reserved.
4 #
5 # Redistribution and use in source and binary forms, with or
6 # without modification, are permitted provided that the following
7 # conditions are met:
8 #
9 #   1. Redistributions of source code must retain the above
10 #      copyright notice, this list of conditions and the following
11 #      disclaimer.
12 #
13 #   2. Redistributions in binary form must reproduce the above
14 #      copyright notice, this list of conditions and the following
15 #      disclaimer in the documentation and/or other materials
16 #      provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30 #
31 # The views and conclusions contained in the software and
32 # documentation are those of the authors and should not be
33 # interpreted as representing official policies, either expressed
34 # or implied, of GRNET S.A.
35
36
37 import os, sys, shutil
38
39 EXCLUDE_FILENAMES = ['.gitignore', 'README', 'version', 'c_bsd.licence',
40                      'python_bsd.licence', 'devflow.conf', 'verify',
41                      'create', 'detach', 'attach', 'parameters.list', 'grow',
42                      'remove', 'python_gpl.licence', 'c_gpl.licence', 'tags',
43                      'config.env', 'distribute_setup.py']
44 GPL_FILES = ['vlmc_wrapper.py', 'kernel/xseg_posix.c', 'kernel/xseg_pthread.c',
45              'xsegbd.c']
46 EXCLUDE_DIRECTORIES = ['.git', 'doc']
47 VALID_YEARS = [2011, 2012, 2013]
48 CUR_YEAR = 2013
49 PYTHON_INTERPRETER = "#!/usr/bin/env python\n"
50 BASH_INTERPRETER = "#!/bin/bash\n"
51 THIS_PATH = os.path.dirname(os.path.realpath(__file__))
52 PYTHON_BSD_LICENCE = open(os.path.join(THIS_PATH, "python_bsd.licence")).readlines()
53 PYTHON_GPL_LICENCE = open(os.path.join(THIS_PATH, "python_gpl.licence")).readlines()
54 C_BSD_LICENCE = open(os.path.join(THIS_PATH, "c_bsd.licence")).readlines()
55 C_GPL_LICENCE = open(os.path.join(THIS_PATH, "c_gpl.licence")).readlines()
56
57
58 class InvalidTypeException(Exception):
59     """Exception to raise for invalid type"""
60     pass
61
62 class LicenceException(Exception):
63     """Exception to raise for licence exception"""
64     pass
65
66 class ExcludedFileException(Exception):
67     """Exception to raise for excluded file"""
68     pass
69
70 class EmptyFileException(Exception):
71     """Exception to raise for empty file"""
72     pass
73
74 class PartialLicenceException(Exception):
75     """exception to raise for partial licence"""
76     pass
77
78 class NoLicenceException(Exception):
79     """Exception to raise for no licence"""
80     pass
81
82 class NoInterpreterException(Exception):
83     """Esception to raise when no interpreter found"""
84     pass
85
86
87 def get_file_type(filename):
88     """Return a string with the type of the file"""
89     for excl_file in EXCLUDE_FILENAMES:
90         if filename.endswith(excl_file):
91             raise ExcludedFileException(filename)
92
93     if filename.endswith('.c') or filename.endswith('.h'):
94         return 'c'
95     elif filename.endswith('.sh'):
96         return 'bash'
97     elif filename.endswith('.py'):
98         return 'python'
99     elif filename.endswith('Makefile') or filename.endswith('.mk'):
100         return 'makefile'
101
102     firstline = open(filename).readline()
103     if firstline == BASH_INTERPRETER:
104         return 'bash'
105     if firstline == PYTHON_INTERPRETER:
106         return 'python'
107
108     raise InvalidTypeException(file)
109
110
111 def __check_licence(filename, licence, year_line, interpreter = None):
112     """Generic test licence function"""
113     fil = open(filename)
114     line = fil.readline()
115     if line == "":
116         raise EmptyFileException("Empty file")
117
118     if interpreter:
119         if line == interpreter:
120             line = fil.readline()
121             if line != "\n":
122                 raise Exception("Blank line is expected after %s",
123                         interpreter)
124             line = fil.readline()
125 #        else:
126 #            raise NoInterpreterException("No interpreter found")
127
128     if line == "":
129         raise EmptyFileException("Empty file")
130
131     if year_line > 0:
132         for i in range(0, year_line):
133             if line != licence[i]:
134                 raise NoLicenceException("No licence")
135             line = fil.readline()
136     found = False
137     for valid_year in VALID_YEARS:
138         licence_line = licence[year_line].replace("#YEAR#", str(valid_year))
139         if line == licence_line:
140             found = True
141             break
142     if not found:
143         raise NoLicenceException("No licence")
144     line = fil.readline()
145     for licence_line in licence[year_line + 1:]:
146         if licence_line != line:
147             print "   Found: " + line
148             print "Expected: " + licence_line
149             raise PartialLicenceException("Partial licence found")
150         line = fil.readline()
151
152
153 def __check_licence2(filename, licence, year_line, interpreter = None,
154                      insert = False):
155     """Generic test or insert licence function"""
156     try:
157         __check_licence(filename, licence, year_line, interpreter)
158     except NoLicenceException:
159         if insert:
160             fil = open(filename)
161             new_filename = filename + '.tmp'
162             new_fil = open(new_filename, 'w')
163             line = fil.readline()
164             if interpreter:
165                 if line == interpreter:
166                     new_fil.write(line)
167                     line = fil.readline()
168                     new_fil.write(line) #mustbe "" otherwise Blankline exception
169                     line = fil.readline()
170             if year_line > 0:
171                 for i in range(0, year_line):
172                     new_fil.write(licence[i])
173             new_fil.write(licence[year_line].replace("#YEAR#", str(CUR_YEAR)))
174             for licence_line in licence[year_line+1:]:
175                 new_fil.write(licence_line)
176             new_fil.write("\n")
177             while line != "":
178                 new_fil.write(line)
179                 line = fil.readline()
180             os.remove(filename)
181             shutil.move(new_filename, filename)
182
183         else:
184             raise NoLicenceException("No licence")
185
186
187
188 def check_licence_python(filename, insert = False):
189     """Check or insert licence in python files"""
190     licence = PYTHON_BSD_LICENCE
191     for gplfile in GPL_FILES:
192         if filename.endswith(gplfile):
193             licence = PYTHON_GPL_LICENCE
194     __check_licence2(filename, licence, 0, PYTHON_INTERPRETER, insert)
195
196
197 def check_licence_bash(filename, insert = False):
198     """Check or insert licence for bash files"""
199     __check_licence2(filename, PYTHON_BSD_LICENCE, 0, BASH_INTERPRETER, insert)
200
201
202 def check_licence_makefile(filename, insert = False):
203     """Check or insert licence for makefiles files"""
204     __check_licence2(filename, PYTHON_BSD_LICENCE, 0, insert = insert)
205
206
207 def check_licence_c(filename, insert = False):
208     """Check or insert licence for c files"""
209     licence = C_BSD_LICENCE
210     for gplfile in GPL_FILES:
211         if filename.endswith(gplfile):
212             licence = C_GPL_LICENCE
213
214     __check_licence2(filename, licence, 1, insert = insert)
215
216
217 if __name__ == "__main__":
218     try:
219         root_dir = sys.argv[1]
220     except:
221         print "Usage: %s path [--insert]" % sys.argv[0]
222         exit(1)
223     try:
224         do_insert = sys.argv[2] == '--insert'
225     except:
226         do_insert = False
227
228     for directory, subdirectories, files in os.walk(root_dir):
229         for ed in EXCLUDE_DIRECTORIES:
230             if ed in subdirectories:
231                 subdirectories.remove(ed)
232         for filen in files:
233             full_path = os.path.join(directory, filen)
234             try:
235                 ft = get_file_type(full_path)
236                 {
237                 'c': check_licence_c,
238                 'python': check_licence_python,
239                 'bash': check_licence_bash,
240                 'makefile': check_licence_makefile
241                 }[ft](full_path, do_insert)
242             except ExcludedFileException:
243                 pass
244             except InvalidTypeException:
245                 print "Invalid type: ", full_path
246             except Exception as e:
247                 print e, " ", full_path