| | 1 | # et:ts=4 |
| | 2 | # portlint.tcl |
| | 3 | # $Id: portlint.tcl $ |
| | 4 | |
| | 5 | package provide portlint 1.0 |
| | 6 | package require portutil 1.0 |
| | 7 | |
| | 8 | set org.macports.lint [target_new org.macports.lint lint_main] |
| | 9 | target_runtype ${org.macports.lint} always |
| | 10 | target_provides ${org.macports.lint} lint |
| | 11 | target_requires ${org.macports.lint} main |
| | 12 | target_prerun ${org.macports.lint} lint_start |
| | 13 | |
| | 14 | set_ui_prefix |
| | 15 | |
| | 16 | set lint_required [list \ |
| | 17 | "name" \ |
| | 18 | "version" \ |
| | 19 | "description" \ |
| | 20 | "long_description" \ |
| | 21 | "categories" \ |
| | 22 | "maintainers" \ |
| | 23 | "platforms" \ |
| | 24 | "homepage" \ |
| | 25 | "master_sites" \ |
| | 26 | "checksums" \ |
| | 27 | ] |
| | 28 | |
| | 29 | set lint_optional [list \ |
| | 30 | "epoch" \ |
| | 31 | "revision" \ |
| | 32 | "worksrcdir" \ |
| | 33 | "distname" \ |
| | 34 | "use_automake" \ |
| | 35 | "use_autoconf" \ |
| | 36 | "use_configure" \ |
| | 37 | ] |
| | 38 | |
| | 39 | |
| | 40 | proc seems_utf8 {str} { |
| | 41 | set len [string length $str] |
| | 42 | for {set i 0} {$i<$len} {incr i} { |
| | 43 | set c [scan [string index $str $i] %c] |
| | 44 | if {$c < 0x80} { |
| | 45 | # ASCII |
| | 46 | continue |
| | 47 | } elseif {($c & 0xE0) == 0xC0} { |
| | 48 | set n 1 |
| | 49 | } elseif {($c & 0xF0) == 0xE0} { |
| | 50 | set n 2 |
| | 51 | } elseif {($c & 0xF8) == 0xF0} { |
| | 52 | set n 3 |
| | 53 | } elseif {($c & 0xFC) == 0xF8} { |
| | 54 | set n 4 |
| | 55 | } elseif {($c & 0xFE) == 0xFC} { |
| | 56 | set n 5 |
| | 57 | } else { |
| | 58 | return false |
| | 59 | } |
| | 60 | for {set j 0} {$j<$n} {incr j} { |
| | 61 | incr i |
| | 62 | if {$i == $len} { |
| | 63 | return false |
| | 64 | } elseif {([scan [string index $str $i] %c] & 0xC0) != 0x80} { |
| | 65 | return false |
| | 66 | } |
| | 67 | } |
| | 68 | } |
| | 69 | return true |
| | 70 | } |
| | 71 | |
| | 72 | |
| | 73 | proc lint_start {args} { |
| | 74 | global UI_PREFIX portname |
| | 75 | ui_msg "$UI_PREFIX [format [msgcat::mc "Verifying Portfile for %s"] ${portname}]" |
| | 76 | } |
| | 77 | |
| | 78 | proc lint_main {args} { |
| | 79 | global UI_PREFIX portname portpath |
| | 80 | set portfile ${portpath}/Portfile |
| | 81 | |
| | 82 | set warnings 0 |
| | 83 | set errors 0 |
| | 84 | |
| | 85 | ################################################################### |
| | 86 | ui_debug "$portfile" |
| | 87 | |
| | 88 | set require_blank false |
| | 89 | set require_after "" |
| | 90 | set seen_portsystem false |
| | 91 | set seen_portgroup false |
| | 92 | set in_description false |
| | 93 | |
| | 94 | set f [open $portfile RDONLY] |
| | 95 | # read binary (to check UTF-8) |
| | 96 | fconfigure $f -encoding binary |
| | 97 | set lineno 1 |
| | 98 | while {1} { |
| | 99 | set line [gets $f] |
| | 100 | if {[eof $f]} { |
| | 101 | close $f |
| | 102 | break |
| | 103 | } |
| | 104 | ui_debug "$lineno: $line" |
| | 105 | |
| | 106 | if {![seems_utf8 $line]} { |
| | 107 | ui_error "Line $lineno seems to contain an invalid UTF-8 sequence" |
| | 108 | incr errors |
| | 109 | } |
| | 110 | |
| | 111 | if {[string equal "PortSystem" $require_after] && \ |
| | 112 | [string match "PortGroup*" $line]} { |
| | 113 | set require_blank false |
| | 114 | } |
| | 115 | |
| | 116 | if {$require_blank && ($line != "")} { |
| | 117 | ui_warn "Line $lineno should be a newline (after $require_after)" |
| | 118 | incr warnings |
| | 119 | } |
| | 120 | set require_blank false |
| | 121 | |
| | 122 | if {[string match "* " $line] || [string match "*\t" $line]} { |
| | 123 | ui_warn "Line $lineno has trailing whitespace before newline" |
| | 124 | incr warnings |
| | 125 | } |
| | 126 | |
| | 127 | if {($lineno == 1) && ![string match "*\$Id*" $line]} { |
| | 128 | ui_warn "Line 1 is missing RCS tag (\$Id)" |
| | 129 | incr warnings |
| | 130 | } elseif {($lineno == 1)} { |
| | 131 | ui_info "OK: Line 1 has RCS tag (\$Id)" |
| | 132 | set require_blank true |
| | 133 | set require_after "RCS tag" |
| | 134 | } |
| | 135 | |
| | 136 | if {[string match "PortSystem*" $line]} { |
| | 137 | if ($seen_portsystem) { |
| | 138 | ui_error "Line $lineno repeats PortSystem information" |
| | 139 | incr errors |
| | 140 | } |
| | 141 | ### TODO: check version |
| | 142 | set seen_portsystem true |
| | 143 | set require_blank true |
| | 144 | set require_after "PortSystem" |
| | 145 | } |
| | 146 | if {[string match "PortGroup*" $line]} { |
| | 147 | if ($seen_portgroup) { |
| | 148 | ui_error "Line $lineno repeats PortGroup information" |
| | 149 | incr errors |
| | 150 | } |
| | 151 | ### TODO: check group |
| | 152 | set seen_portgroup true |
| | 153 | set require_blank true |
| | 154 | set require_after "PortGroup" |
| | 155 | } |
| | 156 | |
| | 157 | # TODO: check tabs and whitespace for variables |
| | 158 | # TODO: check for repeated variable definitions |
| | 159 | # TODO: check the definition order of variables |
| | 160 | # TODO: check length of description against max |
| | 161 | |
| | 162 | if {[string match "long_description*" $line]} { |
| | 163 | set in_description true |
| | 164 | } |
| | 165 | if {$in_description && ([string range $line end end] != "\\")} { |
| | 166 | set in_description false |
| | 167 | set require_blank true |
| | 168 | set require_after "long_description" |
| | 169 | } elseif {$in_description} { |
| | 170 | set require_blank false |
| | 171 | } |
| | 172 | |
| | 173 | ### TODO: more checks to Portfile syntax |
| | 174 | |
| | 175 | incr lineno |
| | 176 | } |
| | 177 | |
| | 178 | ################################################################### |
| | 179 | |
| | 180 | global os.platform os.arch os.version |
| | 181 | global portversion portrevision portepoch |
| | 182 | # hoping for "noarch" : |
| | 183 | set portarch ${os.arch} |
| | 184 | global description long_description categories maintainers platforms homepage master_sites checksums |
| | 185 | |
| | 186 | global lint_required lint_optional |
| | 187 | |
| | 188 | if (!$seen_portsystem) { |
| | 189 | ui_error "Didn't find PortSystem specification" |
| | 190 | incr errors |
| | 191 | } else { |
| | 192 | ui_info "OK: Found PortSystem specification" |
| | 193 | } |
| | 194 | if ($seen_portgroup) { |
| | 195 | ui_info "OK: Found PortGroup specification" |
| | 196 | } |
| | 197 | |
| | 198 | foreach req_var $lint_required { |
| | 199 | if {$req_var == "name"} { |
| | 200 | set var "portname" |
| | 201 | } elseif {$req_var == "version"} { |
| | 202 | set var "portversion" |
| | 203 | } else { |
| | 204 | set var $req_var |
| | 205 | } |
| | 206 | if {![info exists $var]} { |
| | 207 | ui_error "Missing required variable: $req_var" |
| | 208 | incr errors |
| | 209 | } else { |
| | 210 | ui_info "OK: Found required variable: $req_var" |
| | 211 | } |
| | 212 | } |
| | 213 | |
| | 214 | # TODO: check platforms against known names |
| | 215 | # TODO: check categories against known ones |
| | 216 | |
| | 217 | foreach opt_var $lint_optional { |
| | 218 | if {$opt_var == "epoch"} { |
| | 219 | set var "portepoch" |
| | 220 | } elseif {$opt_var == "revision"} { |
| | 221 | set var "portrevision" |
| | 222 | } else { |
| | 223 | set var $opt_var |
| | 224 | } |
| | 225 | if {[info exists $var]} { |
| | 226 | # TODO: check whether it was seen (or default) |
| | 227 | ui_info "OK: Found optional variable: $opt_var" |
| | 228 | } |
| | 229 | } |
| | 230 | |
| | 231 | # TODO: check that ports revision is numeric |
| | 232 | # TODO: check that any port epoch is numeric |
| | 233 | |
| | 234 | if {[string match "*darwinports@opendarwin.org*" $maintainers]} { |
| | 235 | ui_warn "Using legacy email for no/open maintainer" |
| | 236 | incr warnings |
| | 237 | } |
| | 238 | |
| | 239 | ### TODO: more checks to Tcl variables/sections |
| | 240 | |
| | 241 | ui_debug "Name: $portname" |
| | 242 | ui_debug "Epoch: $portepoch" |
| | 243 | ui_debug "Version: $portversion" |
| | 244 | ui_debug "Revision: $portrevision" |
| | 245 | ui_debug "Arch: $portarch" |
| | 246 | ################################################################### |
| | 247 | |
| | 248 | ui_msg "$UI_PREFIX [format [msgcat::mc "%d errors and %d warnings found."] $errors $warnings]" |
| | 249 | |
| | 250 | return {$errors > 0} |
| | 251 | } |