source: trunk/base/src/port1.0/portlint.tcl @ 33560

Last change on this file since 33560 was 33560, checked in by afb@…, 10 years ago

match both port directory and parent directory (#13263)

File size: 12.2 KB
Line 
1# et:ts=4
2# portlint.tcl
3# $Id: portlint.tcl $
4
5package provide portlint 1.0
6package require portutil 1.0
7
8set org.macports.lint [target_new org.macports.lint lint_main]
9target_runtype ${org.macports.lint} always
10target_provides ${org.macports.lint} lint
11target_requires ${org.macports.lint} main
12target_prerun ${org.macports.lint} lint_start
13
14set_ui_prefix
15
16set lint_portsystem \
17        "1.0"
18
19set lint_platforms [list \
20        "macosx" \
21        "darwin" \
22        "freebsd" \
23        "openbsd" \
24        "netbsd" \
25        "linux" \
26        "sunos" \
27        ]
28
29set lint_categories [list \
30        "aqua" \
31        "archivers" \
32        "audio" \
33        "benchmarks" \
34        "cad" \
35        "comms" \
36        "cross" \
37        "databases" \
38        "devel" \
39        "editors" \
40        "emulators" \
41        "fuse" \
42        "games" \
43        "genealogy" \
44        "gnome" \
45        "gnustep" \
46        "graphics" \
47        "iphone" \
48        "irc" \
49        "java" \
50        "kde" \
51        "lang" \
52        "mail" \
53        "math" \
54        "multimedia" \
55        "net" \
56        "news" \
57        "palm" \
58        "perl" \
59        "print" \
60        "python" \
61        "ruby" \
62        "science" \
63        "security" \
64        "shells" \
65        "sysutils" \
66        "tex" \
67        "textproc" \
68        "www" \
69        "x11" \
70        "xfce" \
71        "zope" \
72        ]
73
74set lint_required [list \
75        "name" \
76        "version" \
77        "description" \
78        "long_description" \
79        "categories" \
80        "maintainers" \
81        "platforms" \
82        "homepage" \
83        "master_sites" \
84        "checksums" \
85        ]
86
87set lint_optional [list \
88        "epoch" \
89        "revision" \
90        "worksrcdir" \
91        "distname" \
92        "use_automake" \
93        "use_autoconf" \
94        "use_configure" \
95        ]
96
97set lint_variants [list \
98        "universal" \
99        "docs" \
100        "aqua" \
101        "x11" \
102        ]
103
104
105proc seems_utf8 {str} {
106    set len [string length $str]
107    for {set i 0} {$i<$len} {incr i} {
108        set c [scan [string index $str $i] %c]
109        if {$c < 0x80} {
110            # ASCII
111            continue
112        } elseif {($c & 0xE0) == 0xC0} {
113            set n 1
114        } elseif {($c & 0xF0) == 0xE0} {
115            set n 2
116        } elseif {($c & 0xF8) == 0xF0} {
117            set n 3
118        } elseif {($c & 0xFC) == 0xF8} {
119            set n 4
120        } elseif {($c & 0xFE) == 0xFC} {
121            set n 5
122        } else {
123            return false
124        }
125        for {set j 0} {$j<$n} {incr j} {
126            incr i
127            if {$i == $len} {
128                return false
129            } elseif {([scan [string index $str $i] %c] & 0xC0) != 0x80} {
130                return false
131            }
132        }
133    }
134    return true
135}
136
137
138proc lint_start {args} {
139    global UI_PREFIX portname
140    ui_msg "$UI_PREFIX [format [msgcat::mc "Verifying Portfile for %s"] ${portname}]"
141}
142
143proc lint_main {args} {
144        global UI_PREFIX portname portpath portresourcepath
145        set portfile ${portpath}/Portfile
146        set portdirs [split ${portpath} /]
147        set last [llength $portdirs]
148        incr last -1
149        set portdir [lindex $portdirs $last]
150        incr last -1
151        set portcatdir [lindex $portdirs $last]
152        set groupdir ${portresourcepath}/group
153
154        set warnings 0
155        set errors 0
156
157    ###################################################################
158    ui_debug "$portfile"
159
160    set topline_number 1
161    set require_blank false
162    set require_after ""
163    set seen_portsystem false
164    set seen_portgroup false
165    set in_description false
166
167    set local_variants [list]
168
169    set f [open $portfile RDONLY]
170    # read binary (to check UTF-8)
171    fconfigure $f -encoding binary
172    set lineno 1
173    while {1} {
174        set line [gets $f]
175        if {[eof $f]} {
176            seek $f -1 end
177            set last [read $f 1]
178            if {![string match "\n" $last]} {
179                ui_warn "Line $lineno has missing newline (at end of file)"
180                incr warnings
181            }
182            close $f
183            break
184        }
185        ui_debug "$lineno: $line"
186
187        if {![seems_utf8 $line]} {
188            ui_error "Line $lineno seems to contain an invalid UTF-8 sequence"
189            incr errors
190        }
191
192        if {[string equal "PortSystem" $require_after] && \
193            [string match "PortGroup*" $line]} {
194            set require_blank false
195        }
196
197        if {$require_blank && ($line != "")} {
198            ui_warn "Line $lineno should be a newline (after $require_after)"
199            incr warnings
200        }
201        set require_blank false
202
203        if {[string match "* " $line] || [string match "*\t" $line] &&
204            [string trim $line] != "" } {
205            # allow indented blank lines between blocks of code and such
206            ui_warn "Line $lineno has trailing whitespace before newline"
207            incr warnings
208        }
209
210        if {($lineno == $topline_number) && [string match "*-\*- *" $line]} {
211            ui_info "OK: Line $lineno has emacs/vim Mode"
212            incr topline_number
213        }
214        if {($lineno == $topline_number) && ![string match "*\$Id*\$" $line]} {
215            ui_warn "Line $lineno is missing RCS tag (\$Id\$)"
216            incr warnings
217        } elseif {($lineno == $topline_number)} {
218            ui_info "OK: Line $lineno has RCS tag (\$Id\$)"
219            set require_blank true
220            set require_after "RCS tag"
221        }
222
223        if {[string match "PortSystem*" $line]} {
224            if {$seen_portsystem} {
225                 ui_error "Line $lineno repeats PortSystem information"
226                 incr errors
227            }
228            regexp {PortSystem\s+([0-9.]+)} $line -> portsystem
229            if {![info exists portsystem]} {
230                 ui_error "Line $lineno has unrecognized PortSystem"
231                 incr errors
232            }
233            set seen_portsystem true
234            set require_blank true
235            set require_after "PortSystem"
236        }
237        if {[string match "PortGroup*" $line]} {
238            if {$seen_portgroup} {
239                 ui_error "Line $lineno repeats PortGroup information"
240                 incr errors
241            }
242            regexp {PortGroup\s+([a-z0-9]+)\s+([0-9.]+)} $line -> portgroup portgroupversion
243            if {![info exists portgroup]} {
244                 ui_error "Line $lineno has unrecognized PortGroup"
245                 incr errors
246            }
247            set seen_portgroup true
248            set require_blank true
249            set require_after "PortGroup"
250        }
251
252        # TODO: check for repeated variable definitions
253        # TODO: check the definition order of variables
254        # TODO: check length of description against max
255
256        if {[string match "long_description*" $line]} {
257            set in_description true
258        }
259        if {$in_description && ([string range $line end end] != "\\")} {
260            set in_description false
261            #set require_blank true
262            #set require_after "long_description"
263        } elseif {$in_description} {
264            set require_blank false
265        }
266
267        if {[string match "variant*" $line]} {
268            regexp {variant\s+(\w+)} $line -> variantname
269            if {[info exists variantname]} {
270                 lappend local_variants $variantname
271            }
272        }
273
274        ### TODO: more checks to Portfile syntax
275
276        incr lineno
277    }
278
279    ###################################################################
280
281    global os.platform os.arch os.version
282    global portversion portrevision portepoch
283    # hoping for "noarch" :
284    set portarch ${os.arch}
285    global description long_description platforms categories all_variants
286    global maintainers homepage master_sites checksums patchfiles
287   
288    global lint_portsystem lint_platforms lint_categories
289    global lint_required lint_optional lint_variants
290
291    if (!$seen_portsystem) {
292        ui_error "Didn't find PortSystem specification"
293        incr errors
294    }  elseif {$portsystem != $lint_portsystem} {
295        ui_error "Unknown PortSystem: $portsystem"
296        incr errors
297    } else {
298        ui_info "OK: Found PortSystem $portsystem"
299    }
300    if (!$seen_portgroup) {
301        # PortGroup is optional, so missing is OK
302    }  elseif {![file exists $groupdir/$portgroup-$portgroupversion.tcl]} {
303        ui_error "Unknown PortGroup: $portgroup-$portgroupversion"
304        incr errors
305    } else {
306        ui_info "OK: Found PortGroup $portgroup-$portgroupversion"
307    }
308
309    foreach req_var $lint_required {
310        if {$req_var == "name"} {
311            set var "portname"
312        } elseif {$req_var == "version"} {
313            set var "portversion"
314        } else {
315            set var $req_var
316        }
317       if {![info exists $var]} {
318            ui_error "Missing required variable: $req_var"
319            incr errors
320        } else {
321            ui_info "OK: Found required variable: $req_var"
322        }
323    }
324
325    foreach opt_var $lint_optional {
326       if {$opt_var == "epoch"} {
327            set var "portepoch"
328        } elseif {$opt_var == "revision"} {
329            set var "portrevision"
330        } else {
331            set var $opt_var
332       }
333       if {[info exists $var]} {
334            # TODO: check whether it was seen (or default)
335            ui_info "OK: Found optional variable: $opt_var"
336       }
337    }
338
339    if {[info exists platforms]} {
340        foreach platform $platforms {
341           if {[lsearch -exact $lint_platforms $platform] == -1} {
342                ui_error "Unknown platform: $platform"
343                incr errors
344            } else {
345                ui_info "OK: Found platform: $platform"
346            }
347        }
348    }
349
350    if {[info exists categories]} {
351        set category [lindex $categories 0]
352        if {[lsearch -exact $lint_categories $category] == -1} {
353            ui_error "Unknown category: $category"
354            incr errors
355        } else {
356            ui_info "OK: Found category: $category"
357        }
358        foreach secondary $categories {
359            if {[string match $secondary $category]} {
360                continue
361            }
362            ui_info "OK: Found category: $secondary"
363        }
364    }
365
366    if {![string is integer -strict $portepoch]} {
367        ui_error "Port epoch is not numeric:  $portepoch"
368        incr errors
369    }
370    if {![string is integer -strict $portrevision]} {
371        ui_error "Port revision is not numeric: $portrevision"
372        incr errors
373    }
374
375    set variantnumber 1
376    foreach variant $all_variants {
377        set variantname [ditem_key $variant name] 
378        set variantdesc [lindex [ditem_key $variant description] 0]
379        if {![info exists variantname] || $variantname == ""} {
380            ui_error "Variant number $variantnumber does not have a name"
381            incr errors
382        } elseif {![info exists variantdesc] || $variantdesc == ""} {
383            ui_info "OK: Found variant: $variantname"
384            # don't warn about missing descriptions for global variants
385            if {[lsearch -exact $local_variants $variantname] != -1 &&
386                [lsearch -exact $lint_variants $variantname] == -1} {
387                ui_warn "Variant $variantname does not have a description"
388                incr warnings
389            }
390        } else {
391            ui_info "OK: Found variant $variantname: $variantdesc"
392        }
393        incr variantnumber
394    }
395
396    if {[string match "*darwinports@opendarwin.org*" $maintainers]} {
397        ui_warn "Using legacy email address for no/open maintainer"
398        incr warnings
399    }
400
401    if {[string match "*nomaintainer@macports.org*" $maintainers] ||
402        [string match "*openmaintainer@macports.org*" $maintainers]} {
403        ui_warn "Using full email address for no/open maintainer"
404        incr warnings
405    }
406
407    # these checks are only valid for ports stored in the regular tree directories
408    if {$portcatdir != $category} {
409        ui_error "Portfile parent directory $portcatdir does not match primary category $category"
410        incr errors
411    } else {
412        ui_info "OK: Portfile parent directory matches primary category"
413    }
414    if {$portdir != $portname} {
415        ui_error "Portfile directory $portdir does not match port name $portname"
416        incr errors
417    } else {
418        ui_info "OK: Portfile directory matches port name"
419    }
420
421    if {[info exists patchfiles]} {
422        foreach patchfile $patchfiles {
423            if {![string match "patch-*.diff" $patchfile]} {
424                ui_warn "Patchfile $patchfile does not follow the source patch naming policy \"patch-*.diff\""
425                incr warnings
426            }
427        }
428    }
429
430    ### TODO: more checks to Tcl variables/sections
431
432    ui_debug "Name: $portname"
433    ui_debug "Epoch: $portepoch"
434    ui_debug "Version: $portversion"
435    ui_debug "Revision: $portrevision"
436    ui_debug "Arch: $portarch"
437    ###################################################################
438
439        ui_msg "$UI_PREFIX [format [msgcat::mc "%d errors and %d warnings found."] $errors $warnings]"
440
441        return {$errors > 0}
442}
Note: See TracBrowser for help on using the repository browser.