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

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

Added finance and gis as primary categories.

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