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

Last change on this file since 37981 was 37981, checked in by ryandesign@…, 9 years ago

portlint.tcl: warn when using "configure {}" instead of "use_configure no"; closes #15806

File size: 14.3 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_state ${org.macports.lint} no
11target_provides ${org.macports.lint} lint
12target_requires ${org.macports.lint} main
13target_prerun ${org.macports.lint} lint_start
14
15set_ui_prefix
16
17set lint_portsystem \
18        "1.0"
19
20set lint_platforms [list \
21        "macosx" \
22        "darwin" \
23        "freebsd" \
24        "openbsd" \
25        "netbsd" \
26        "linux" \
27        "sunos" \
28        ]
29
30set lint_categories [list \
31        "aqua" \
32        "archivers" \
33        "audio" \
34        "benchmarks" \
35        "cad" \
36        "comms" \
37        "cross" \
38        "databases" \
39        "devel" \
40        "editors" \
41        "emulators" \
42        "fuse" \
43        "games" \
44        "genealogy" \
45        "gnome" \
46        "gnustep" \
47        "graphics" \
48        "iphone" \
49        "irc" \
50        "java" \
51        "kde" \
52        "lang" \
53        "mail" \
54        "math" \
55        "multimedia" \
56        "net" \
57        "news" \
58        "palm" \
59        "perl" \
60        "print" \
61        "python" \
62        "ruby" \
63        "science" \
64        "security" \
65        "shells" \
66        "sysutils" \
67        "tex" \
68        "textproc" \
69        "www" \
70        "x11" \
71        "xfce" \
72        "zope" \
73        ]
74
75set lint_required [list \
76        "name" \
77        "version" \
78        "description" \
79        "long_description" \
80        "categories" \
81        "maintainers" \
82        "platforms" \
83        "homepage" \
84        "master_sites" \
85        "checksums" \
86        ]
87
88set lint_optional [list \
89        "epoch" \
90        "revision" \
91        "worksrcdir" \
92        "distname" \
93        "use_automake" \
94        "use_autoconf" \
95        "use_configure" \
96        ]
97
98set lint_variants [list \
99        "universal" \
100        "docs" \
101        "aqua" \
102        "x11" \
103        ]
104
105
106proc seems_utf8 {str} {
107    set len [string length $str]
108    for {set i 0} {$i<$len} {incr i} {
109        set c [scan [string index $str $i] %c]
110        if {$c < 0x80} {
111            # ASCII
112            continue
113        } elseif {($c & 0xE0) == 0xC0} {
114            set n 1
115        } elseif {($c & 0xF0) == 0xE0} {
116            set n 2
117        } elseif {($c & 0xF8) == 0xF0} {
118            set n 3
119        } elseif {($c & 0xFC) == 0xF8} {
120            set n 4
121        } elseif {($c & 0xFE) == 0xFC} {
122            set n 5
123        } else {
124            return false
125        }
126        for {set j 0} {$j<$n} {incr j} {
127            incr i
128            if {$i == $len} {
129                return false
130            } elseif {([scan [string index $str $i] %c] & 0xC0) != 0x80} {
131                return false
132            }
133        }
134    }
135    return true
136}
137
138
139proc lint_start {args} {
140    global UI_PREFIX portname
141    ui_msg "$UI_PREFIX [format [msgcat::mc "Verifying Portfile for %s"] ${portname}]"
142}
143
144proc lint_main {args} {
145        global UI_PREFIX portname portpath portresourcepath
146        set portfile ${portpath}/Portfile
147        set portdirs [split ${portpath} /]
148        set last [llength $portdirs]
149        incr last -1
150        set portdir [lindex $portdirs $last]
151        incr last -1
152        set portcatdir [lindex $portdirs $last]
153        set groupdir ${portresourcepath}/group
154
155        set warnings 0
156        set errors 0
157
158    ###################################################################
159    ui_debug "$portfile"
160
161    set topline_number 1
162    set require_blank false
163    set require_after ""
164    set seen_portsystem false
165    set seen_portgroup false
166    set in_description false
167
168    set local_variants [list]
169
170    set f [open $portfile RDONLY]
171    # read binary (to check UTF-8)
172    fconfigure $f -encoding binary
173    set lineno 1
174    while {1} {
175        set line [gets $f]
176        if {[eof $f]} {
177            seek $f -1 end
178            set last [read $f 1]
179            if {![string match "\n" $last]} {
180                ui_warn "Line $lineno has missing newline (at end of file)"
181                incr warnings
182            }
183            close $f
184            break
185        }
186        ui_debug "$lineno: $line"
187
188        if {![seems_utf8 $line]} {
189            ui_error "Line $lineno seems to contain an invalid UTF-8 sequence"
190            incr errors
191        }
192
193        if {[string equal "PortSystem" $require_after] && \
194            [string match "PortGroup*" $line]} {
195            set require_blank false
196        }
197
198        if {$require_blank && ($line != "")} {
199            ui_warn "Line $lineno should be a newline (after $require_after)"
200            incr warnings
201        }
202        set require_blank false
203
204        if {[regexp {\S[ \t]+$} $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        if {[regexp {(^|\s)configure\s+\{\s*\}} $line]} {
275            ui_warn "Line $lineno should say \"use_configure no\" instead of declaring an empty configure phase"
276            incr warnings
277        }
278
279        ### TODO: more checks to Portfile syntax
280
281        incr lineno
282    }
283
284    ###################################################################
285
286    global os.platform os.arch os.version
287    global portversion portrevision portepoch
288    # hoping for "noarch" :
289    set portarch ${os.arch}
290    global description long_description platforms categories all_variants
291    global maintainers homepage master_sites checksums patchfiles
292    global depends_lib depends_build depends_run fetch.type
293   
294    global lint_portsystem lint_platforms lint_categories
295    global lint_required lint_optional lint_variants
296
297    if (!$seen_portsystem) {
298        ui_error "Didn't find PortSystem specification"
299        incr errors
300    }  elseif {$portsystem != $lint_portsystem} {
301        ui_error "Unknown PortSystem: $portsystem"
302        incr errors
303    } else {
304        ui_info "OK: Found PortSystem $portsystem"
305    }
306    if (!$seen_portgroup) {
307        # PortGroup is optional, so missing is OK
308    }  elseif {![file exists $groupdir/$portgroup-$portgroupversion.tcl]} {
309        ui_error "Unknown PortGroup: $portgroup-$portgroupversion"
310        incr errors
311    } else {
312        ui_info "OK: Found PortGroup $portgroup-$portgroupversion"
313    }
314
315    foreach req_var $lint_required {
316        if {$req_var == "name"} {
317            set var "portname"
318        } elseif {$req_var == "version"} {
319            set var "portversion"
320        } else {
321            set var $req_var
322        }
323
324       if {$var == "master_sites" && ${fetch.type} != "standard"} {
325             ui_info "OK: $var not required for fetch.type ${fetch.type}"
326             continue
327       }
328       
329       if {![info exists $var]} {
330            ui_error "Missing required variable: $req_var"
331            incr errors
332        } else {
333            ui_info "OK: Found required variable: $req_var"
334        }
335    }
336
337    foreach opt_var $lint_optional {
338       if {$opt_var == "epoch"} {
339            set var "portepoch"
340        } elseif {$opt_var == "revision"} {
341            set var "portrevision"
342        } else {
343            set var $opt_var
344       }
345       if {[info exists $var]} {
346            # TODO: check whether it was seen (or default)
347            ui_info "OK: Found optional variable: $opt_var"
348       }
349    }
350
351    if {[info exists platforms]} {
352        foreach platform $platforms {
353           if {[lsearch -exact $lint_platforms $platform] == -1} {
354                ui_error "Unknown platform: $platform"
355                incr errors
356            } else {
357                ui_info "OK: Found platform: $platform"
358            }
359        }
360    }
361
362    if {[info exists categories]} {
363        set category [lindex $categories 0]
364        if {[lsearch -exact $lint_categories $category] == -1} {
365            ui_error "Unknown category: $category"
366            incr errors
367        } else {
368            ui_info "OK: Found category: $category"
369        }
370        foreach secondary $categories {
371            if {[string match $secondary $category]} {
372                continue
373            }
374            ui_info "OK: Found category: $secondary"
375        }
376    }
377
378    if {![string is integer -strict $portepoch]} {
379        ui_error "Port epoch is not numeric:  $portepoch"
380        incr errors
381    }
382    if {![string is integer -strict $portrevision]} {
383        ui_error "Port revision is not numeric: $portrevision"
384        incr errors
385    }
386
387    set variantnumber 1
388    foreach variant $all_variants {
389        set variantname [ditem_key $variant name] 
390        set variantdesc [lindex [ditem_key $variant description] 0]
391        if {![info exists variantname] || $variantname == ""} {
392            ui_error "Variant number $variantnumber does not have a name"
393            incr errors
394        } else {
395            set name_ok true
396            set desc_ok true
397
398            if {![regexp {^[A-Za-z0-9_]+$} $variantname]} {
399                ui_error "Variant name $variantname is not valid; use \[A-Za-z0-9_\]+ only"
400                incr errors
401                set name_ok false
402            }
403
404            if {![info exists variantdesc] || $variantdesc == ""} {
405                # don't warn about missing descriptions for global variants
406                if {[lsearch -exact $local_variants $variantname] != -1 &&
407                    [lsearch -exact $lint_variants $variantname] == -1} {
408                    ui_warn "Variant $variantname does not have a description"
409                    incr warnings
410                    set desc_ok false
411                } elseif {$variantdesc == ""} {
412                    set variantdesc "(pre-defined variant)"
413                }
414            }
415
416            if {$name_ok} {
417                if {$desc_ok} {
418                    ui_info "OK: Found variant $variantname: $variantdesc"
419                } else {
420                    ui_info "OK: Found variant: $variantname"
421                }
422            }
423        }
424        incr variantnumber
425    }
426
427    set all_depends {}
428    if {[info exists depends_lib]} { eval "lappend all_depends $depends_lib" }
429    if {[info exists depends_build]} { eval "lappend all_depends $depends_build" }
430    if {[info exists depends_run]} { eval "lappend all_depends $depends_run" }
431    foreach depspec $all_depends {
432        set dep [lindex [split $depspec :] end]
433        if {[catch {set res [mport_search "^$dep\$"]} error]} {
434            global errorInfo
435            ui_debug "$errorInfo"
436            continue
437        }
438        if {$res == ""} {
439            ui_error "Unknown dependency: $dep"
440            incr errors
441        } else {
442            ui_info "OK: Found dependency: $dep"
443        }
444    }
445
446    if {[regexp "^(.+)nomaintainer(@macports.org)?(.+)$" $maintainers] } {
447        ui_error "Using nomaintainer together with other maintainer"
448        incr errors
449    }
450
451    if {[regexp "^openmaintainer(@macports.org)?$" $maintainers] } {
452        ui_error "Using openmaintainer without any other maintainer"
453        incr errors
454    }
455
456    if {[string match "*darwinports@opendarwin.org*" $maintainers]} {
457        ui_warn "Using legacy email address for no/open maintainer"
458        incr warnings
459    }
460
461    if {[string match "*nomaintainer@macports.org*" $maintainers] ||
462        [string match "*openmaintainer@macports.org*" $maintainers]} {
463        ui_warn "Using full email address for no/open maintainer"
464        incr warnings
465    }
466
467    # these checks are only valid for ports stored in the regular tree directories
468    if {$portcatdir != $category} {
469        ui_error "Portfile parent directory $portcatdir does not match primary category $category"
470        incr errors
471    } else {
472        ui_info "OK: Portfile parent directory matches primary category"
473    }
474    if {$portdir != $portname} {
475        ui_error "Portfile directory $portdir does not match port name $portname"
476        incr errors
477    } else {
478        ui_info "OK: Portfile directory matches port name"
479    }
480
481    if {[info exists patchfiles]} {
482        foreach patchfile $patchfiles {
483            if {![string match "patch-*.diff" $patchfile] && [file exists "$portpath/files/$patchfile"]} {
484                ui_warn "Patchfile $patchfile does not follow the source patch naming policy \"patch-*.diff\""
485                incr warnings
486            }
487        }
488    }
489
490    ### TODO: more checks to Tcl variables/sections
491
492    ui_debug "Name: $portname"
493    ui_debug "Epoch: $portepoch"
494    ui_debug "Version: $portversion"
495    ui_debug "Revision: $portrevision"
496    ui_debug "Arch: $portarch"
497    ###################################################################
498
499        ui_msg "$UI_PREFIX [format [msgcat::mc "%d errors and %d warnings found."] $errors $warnings]"
500
501        return {$errors > 0}
502}
Note: See TracBrowser for help on using the repository browser.