~ubuntu-app-review-contributors/arb-lint/trunk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import re
import os

from . depends import provides, requires
from . output import error, warning
import checks

class SourcePackage(object):
    package_name = None
    source_files = []
    is_lens = False
    is_distutils = False
    is_quickly_app = False
    has_copyright = False
    has_debian_control = False
    has_debian_rules = False
    has_debian_changelog = False

    @provides(['cwd', 'debian_dir'])
    def __init__(self):
        self.cwd = os.getcwd()
        self.debian_dir = os.path.join(self.cwd, "debian")

    @provides('package_name')
    def find_package_name(self):
        changelog = os.path.join(self.debian_dir, "changelog")
        if not os.path.exists(changelog):
            return None
        f = open(changelog, "r")
        line = f.readline()
        f.close()
        self.package_name = line.split()[0]

    @provides('source_files')
    def find_source_files(self):
        if self.source_files:
            return None
        for path, dirs, files in os.walk(self.cwd):
            for filename in files:
                self.source_files += [os.path.join(path, filename)]
        prefix = os.path.commonprefix(self.source_files)
        self.source_files = map(lambda a: a.split(prefix)[1],
                                self.source_files)
        self.source_files = filter(lambda a: not a.startswith(".bzr") and \
                                             not a.startswith("debian"),
                                   self.source_files)

    @provides('is_lens')
    @requires('source_files')
    def find_out_if_this_is_a_lens(self):
        if filter(lambda a: a.endswith(".service") or a.endswith(".lens"),
                  self.source_files):
            self.is_lens = True

    @provides('is_distutils')
    def find_out_if_this_is_distutils(self):
        if os.path.exists(os.path.join(self.cwd, 'setup.py')):
            self.is_distutils = True

    @provides('is_distutils')
    def find_out_if_this_is_quickly_app(self):
        if os.path.exists(os.path.join(self.cwd, '.quickly')):
            self.is_quickly_app = True

    @requires('debian_dir')
    def test_has_debian_dir(self):
        if not os.path.exists(self.debian_dir):
            warning("This submission seems not to be packaged yet. You might "
                    "want to reply with something along these lines:"
                    "---"
                    "Thanks for your submission! We've created a simple "
                    "walk-through guide in how to package an application to "
                    "meet the requirements for this submission process."
                    ""
                    "https://wiki.ubuntu.com/AppReviewBoard/Submissions/PackageQuickStart"
                    "Please follow these steps for your application. If the "
                    "instructions are unclear at any point, it would help us "
                    "(and other developers) if you ask us questions about "
                    "what you need to do, so we can improve the guide. You "
                    "can contact us by submitting a reply to the website, on "
                    "the app-review-board@lists.ubuntu.com mailing list, or "
                    "#ubuntu-app-devel IRC channel on Freenode.")

    @provides('has_copyright')
    @requires('debian_dir')
    def test_has_copyright(self):
        copyright_file = os.path.join(self.debian_dir, "copyright")
        if checks.file_is_missing_or_empty(copyright_file):
            error("debian/copyright is missing or empty. Please have a look "
                  "at http://dep.debian.net/deps/dep5/ to find out how to fix "
                  "this.")
        else:
            self.has_copyright = True

    @requires('has_copyright')
    def test_uses_dep5(self):
        copyright_file = os.path.join(self.debian_dir, "copyright")
        first_line = open(copyright_file, "r").readline()
        values = [a.strip() for a in first_line.split()]
        if values[0] != "Format:" or \
           values[1] not in ["http://dep.debian.net/deps/dep5",
                             "http://dep.debian.net/deps/dep5/",
                             "http://www.debian.org/doc/packaging-manuals/copyright-format/1.0",
                             "http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/"]:
            warning("This package does not use DEP-5, which provides a "
                    "machine-readable debian/copyright file. You might want "
                    "to review "
                    "http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ "
                    "to find out how to accomplish it. "
                    "http://bazaar.launchpad.net/~dholbach/ubuntu-app-reviews/harvestwidget/view/head:/debian/copyright "
                    "has a live example for a simple app.")

    @provides('has_debian_control')
    @requires('debian_dir')
    def test_has_debian_control(self):
        control_file = os.path.join(self.debian_dir, "control")
        if checks.file_is_missing_or_empty(control_file):
            error("debian/control seems to be missing or empty.")
        else:
            self.has_debian_control = True

    @provides('has_debian_rules')
    @requires('debian_dir')
    def test_has_debian_rules(self):
        rules_file = os.path.join(self.debian_dir, "rules")
        if checks.file_is_missing_or_empty(rules_file):
            error("debian/rules seems to be missing or empty.")
        else:
            self.has_debian_rules = True

    @provides('has_debian_changelog')
    @requires('debian_dir')
    def test_has_debian_changelog(self):
        changelog_file = os.path.join(self.debian_dir, "changelog")
        if checks.file_is_missing_or_empty(changelog_file):
            error("debian/changelog seems to be missing or empty.")
        else:
            self.has_debian_changelog = True

    @requires('has_debian_control')
    def test_uses_deprecated_python_installation(self):
        control_file = os.path.join(self.debian_dir, "control")
        with open(control_file, "r") as f:
            contents = f.read()
        if 'python-support' in contents or \
           'python-central' in contents:
            warning("This package uses a deprecated python installation "
                    "tool. You might want to have a look at "
                    "http://wiki.debian.org/Python/TransitionToDHPython2 "
                    "to find out how to transition to dh_python2.")

    @requires('has_debian_control')
    def test_for_uptodate_standards_version(self):
        control_file = os.path.join(self.debian_dir, "control")
        with open(control_file, "r") as f:
            contents = f.readlines()
        if 'Standards-Version: 3.9.3\n' not in contents:
            warning("You might want to update the Standards-Version in "
                    "debian/control to version 3.9.3.")

    @requires('source_files')
    def test_is_lightweight_app(self):
        source_file_extensions = [ ".py", ".rb", ".vala", ".c", ".h", ".cpp", 
                ".cc", ".hh", ".pm", ".java", ".js", ".pl" ]
        number_of_max_source_files = 30
        number_of_max_lines_of_code = 15000
        source_code_files = filter(lambda a: os.path.splitext(a)[1] in source_file_extensions,
                                   self.source_files)
        loc = sum(map(lambda a: len(open(a).readlines()), source_code_files))
        if len(source_code_files) >= number_of_max_source_files or \
           loc >= number_of_max_lines_of_code:
            warning("This app might be too big to be reviewed by the ARB. "
                    "It has %s source files and %s lines of code. "
                    "This might serve as reply to the app submitter: "
                    "We are sorry to inform you that this app is outside the "
                    "scope of the App Review Board. Our focus is on "
                    "lightweight apps. We are generally looking for the kind "
                    "of apps which could be reviewed for functionality and "
                    "security in about an hour reading through the code. "
                    "https://wiki.ubuntu.com/UbuntuDevelopment/NewPackages "
                    "might suit you better." % \
                            (len(source_code_files), loc))

    @requires('debian_dir')
    def test_has_maintainer_scripts(self):
        offending_files = []
        prefixes = [ "post", "pre" ]
        suffixes = [ "rm", "inst" ]
        for prefix in prefixes:
            for suffix in suffixes:
                which = prefix+suffix
                for filename in [ "debian/%s" % which, 
                        "debian/%s.%s"% (self.package_name, which) ]:
                    if os.path.exists(filename):
                        offending_files += [ filename ]
        if offending_files:
            error("This app includes maintainer scripts. The scope of apps "
                  "is for them to be small and unintrusive, so the use of "
                  "maintainer scripts is not allowed. These files should "
                  "not be used:", offending_files)

    @requires('has_debian_control')
    def test_conflicts_replaces_breaks(self):
        if not self.has_debian_control:
            return
        control_file = os.path.join(self.debian_dir, "control")
        with open(control_file, "r") as f:
            contents = f.readlines()
        if filter(lambda a: a.startswith("Breaks:") or \
                            a.startswith("Conflicts:") or \
                            a.startswith("Replaces:"), contents):
            warning("This app seems to specify a Breaks or Conflicts or "
                    "Replaces in debian/control. As apps are supposed to be "
                    "self-contained without relationships to other packages "
                    "this might be a mistake or a problem.")

    @requires(['has_debian_rules', 'has_debian_control'])
    def test_for_unused_cdbs(self):
        if not self.has_debian_rules or not self.has_debian_control:
            return
        control_file = os.path.join(self.debian_dir, "control")
        rules_file = os.path.join(self.debian_dir, "rules")
        with open(control_file, "r") as f:
            control_contents = f.read()
        with open(rules_file, "r") as f:
            rules_contents = f.read()
        if "cdbs" in control_contents and \
           not "usr/share/cdbs/1" in rules_contents:
            warning("This app seems to list cdbs as a Build-Depends in "
                    "debian/control, but does not use it in debian/rules. "
                    "It should be safe to remove it in debian/control.")

    @requires('has_debian_changelog')
    def test_for_multiple_changelog_entries(self):
        if not self.has_debian_changelog:
            return
        changelog_file = os.path.join(self.debian_dir, "changelog")
        with open(changelog_file, "r") as f:
            contents = f.read()
        entries = re.findall(r'(\S+? \(\S+?\) \S+?\; urgency\=\S+?\s)', contents)
        if len(entries) > 1:
            warning("The ARB wants only one changelog entry in "
                    "debian/changelog, so you might want to collate all "
                    "the relevant information in one changelog entry under "
                    "the last version and date.")