source: branches/gsoc09-logging/base/src/port1.0/portlint.tcl @ 52218

Last change on this file since 52218 was 52218, checked in by enl@…, 11 years ago

Merge from trunk

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