|  | <?php | 
|  | /* | 
|  | * A plugin/feature version checking auditor with support for cli and www mode. | 
|  | */ | 
|  | define("LOGGER_FAIL", 0); | 
|  | define("LOGGER_OK", 1); | 
|  | define("LOGGER_INFO", 2); | 
|  | define("LOGGER_SQL", 10); | 
|  |  | 
|  | require_once ("buildServer-common.php"); | 
|  |  | 
|  | $verbosity = 0; | 
|  | $cli = isset($argv); // $argv is only defined when running in cli mode | 
|  |  | 
|  | /* in cli mode, you'll need to have the includes directory (with db.php) in the current directory, then invoke this script directly, rather than one of the placeholders */ | 
|  | if ($cli) | 
|  | { | 
|  | $require_db = "includes/db.php"; | 
|  |  | 
|  | $dirs = array(); | 
|  | for ($i = 1; $i < sizeof($argv); $i++) | 
|  | { | 
|  | $m = null; | 
|  | if (preg_match("/^-(v+)$/", $argv[$i], $m)) | 
|  | { | 
|  | $verbosity = strlen($m[1]); | 
|  | } | 
|  | else if (is_dir($argv[$i])) | 
|  | { | 
|  | $dirs[] = $argv[$i]; | 
|  | } | 
|  | else | 
|  | { | 
|  | print "$argv[$i] wasn't a directory or a verbosity flag, I don't know what to do with it!\n"; | 
|  | exit(-4); | 
|  | } | 
|  | } | 
|  |  | 
|  | $html = false; | 
|  | } | 
|  | else | 
|  | { | 
|  | require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/app.class.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/nav.class.php"); require_once($_SERVER['DOCUMENT_ROOT'] . "/eclipse.org-common/system/menu.class.php"); $App = new App(); $Nav = new Nav(); $Menu = new Menu(); include($App->getProjectCommon()); | 
|  |  | 
|  | $require_db = $_SERVER["DOCUMENT_ROOT"] . "/modeling/includes/db.php"; | 
|  |  | 
|  | if ((isset($_GET["debug"]) && preg_match("/(^\d+$)/", $_GET["debug"], $m)) || (isset($_GET["verbosity"]) && preg_match("/(^\d+$)/", $_GET["verbosity"], $m))) | 
|  | { | 
|  | $verbosity = $m[1]; | 
|  | } | 
|  |  | 
|  | /* here we inherit $dirs from the placeholder file that includes us */ | 
|  | if (isset($_GET["branch"])) | 
|  | { | 
|  | $b = $_GET["branch"]; | 
|  | if (isset($dirs[$b])) | 
|  | { | 
|  | $dirs = array($dirs[$b]); | 
|  | } | 
|  | else | 
|  | { | 
|  | header("Content-type: text/html"); | 
|  | print "<pre>$b wasn't a valid branch, please try again with a valid branch, such as:\n"; | 
|  | print join("\n", preg_replace("/^(.+)$/", "- <a href=\"?branch=$1\">$1</a>", array_keys($dirs))) . "</pre>\n"; | 
|  | exit(-5); | 
|  | } | 
|  | } | 
|  |  | 
|  | foreach ($dirs as $dir) | 
|  | { | 
|  | if (!is_dir($dir)) | 
|  | { | 
|  | print "$dir wasn't a directory, please amend the definition of \$dirs in {$_SERVER["PHP_SELF"]}\n"; | 
|  | exit(-4); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (isset($_GET["html"])) | 
|  | { | 
|  | $html = true; | 
|  | header("Content-type: text/html"); | 
|  | } | 
|  | else | 
|  | { | 
|  | $html = false; | 
|  | header("Content-type: text/plain"); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!isset($dirs) || !is_array($dirs) || sizeof($dirs) == 0) | 
|  | { | 
|  | print "I need to know what project/component you'd like me to audit!\n\n"; | 
|  | if ($cli) | 
|  | { | 
|  | print "For example:\n"; | 
|  | print "\tphp $argv[0] org.eclipse.emf\n\n"; | 
|  | print "You may also add -v or -vv for greater verbosity.\n"; | 
|  | } | 
|  | else | 
|  | { | 
|  | print "Make sure \$dirs is defined in {$_SERVER["PHP_SELF"]} and contains a list of branches => directories you'd like to process.\n"; | 
|  | } | 
|  | exit(-1); | 
|  | } | 
|  |  | 
|  | ob_start(); | 
|  |  | 
|  | foreach ($dirs as $dir) | 
|  | { | 
|  | $r = trim(file_get_contents("$dir/CVS/Repository")); | 
|  | $m = null; | 
|  | if (preg_match("#^(org\.eclipse\.[^/]+)(?:/([^/]+))?$#", $r, $m)) | 
|  | { | 
|  | $proj = $m[1]; | 
|  | $com = (isset($m[2]) ? $m[2] : ""); | 
|  | logger(LOGGER_INFO, "found $proj/$com\n"); | 
|  | } | 
|  | else | 
|  | { | 
|  | $msg = "I couldn't figure out what project/component that is, quitting...\n"; | 
|  | $msg .= "     --> make sure $dir/CVS/Repository exists and is correct\n"; | 
|  | logger(LOGGER_FAIL, $msg); | 
|  | exit(-3); | 
|  | } | 
|  |  | 
|  | $branch = "HEAD"; | 
|  | if (is_file("$dir/CVS/Tag")) | 
|  | { | 
|  | $branch = preg_replace("/^./", "", trim(file_get_contents("$dir/CVS/Tag"))); | 
|  | logger(LOGGER_INFO, "detected branch $branch\n"); | 
|  | } | 
|  | else | 
|  | { | 
|  | logger(LOGGER_INFO, "no CVS/Tag file found, assuming HEAD branch\n"); | 
|  | } | 
|  |  | 
|  | $branchfails = 0; | 
|  | $issues = array(); | 
|  | require_once($require_db); | 
|  | foreach (glob("$dir/{plugins,tests,examples}/org.eclipse.*", GLOB_BRACE) as $plugdir) | 
|  | { | 
|  | $plugin = basename($plugdir); | 
|  | $type = preg_replace("#^.+/([^/]+)/$plugin$#", "$1", $plugdir); //plugins, tests, or examples | 
|  |  | 
|  | $deps = array(); | 
|  | $checked = array(); | 
|  | $queue = array($plugin); | 
|  | $versions = array(); | 
|  | $lastversions = array(); | 
|  | $vcache = array(); | 
|  | $fails = 0; | 
|  |  | 
|  | if (preg_match("/-feature$/", $plugin)) | 
|  | { | 
|  | logger(LOGGER_INFO, "skipping $plugdir as it looks like a feature, not a plugin\n\n"); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if ($tmp = plugin_version($plugdir)) | 
|  | { | 
|  | $vanityname = preg_replace("/\.qualifier$/", "", $tmp); | 
|  | $version = convert_version($tmp); | 
|  |  | 
|  | $lastdir = preg_replace("#cvssrc(?:_branches)?(/" . basename($dir) . ")#", "cvssrc_branches$1-latest", $dir); | 
|  | $lastplugdir = preg_replace("#cvssrc(?:_branches)?(/" . basename($dir) . ")#", "cvssrc_branches$1-latest", $plugdir); | 
|  | if (!is_dir($lastplugdir)) | 
|  | { | 
|  | logger(LOGGER_INFO, "$lastplugdir does not exist, skipping all checks on $plugdir\n"); | 
|  | continue; | 
|  | } | 
|  | $lastversion = preg_replace("/\.qualifier$/", "", plugin_version($lastplugdir)); | 
|  | if (is_dir($lastplugdir)) | 
|  | { | 
|  | logger(LOGGER_INFO, "Last released version of $lastplugdir is $lastversion\n"); | 
|  | } | 
|  |  | 
|  | $p = ($com == "" ? $proj : "$proj/$com"); | 
|  |  | 
|  | /* it's quite possible for us to end up with changes in a branch (say R2_1_maintenance) with the plugin only being versioned at 2.1.0, of course 2.1.0 wasn't released from the R2_1_maintenance branch, so we'll never find it there | 
|  | * keep trying to find the last build in progressively less picky ways... */ | 
|  | $lastbuild = array( | 
|  | "(SELECT MAX(`buildtime`) FROM `releases` WHERE `project` = '$proj' AND `component` = '$com' AND `branch` = '$branch' AND `type` = 'R')", | 
|  | "(SELECT `buildtime` FROM `releases` WHERE `project` = '$proj' AND `component` = '$com' AND `vanityname` = '$vanityname' AND `type` = 'R')", | 
|  | "(SELECT MIN(`buildtime`) FROM `releases` WHERE `project` = '$proj' AND `component` = '$com' AND `type` = 'R')", | 
|  | "'2000-01-01'" | 
|  | ); | 
|  | $query = "SELECT `bugid`, `cvsname`, `date` FROM `cvsfiles` NATURAL JOIN `commits` NATURAL LEFT JOIN `bugs` WHERE `project` = '$proj' AND `branch` = '$branch' AND `cvsname` LIKE '/cvsroot/modeling/$p/$type/$plugin/%' AND `date` >= COALESCE(" . join(", ", $lastbuild) . ")"; | 
|  | $result = wmysql_query($query); | 
|  | if (mysql_num_rows($result) == 0) | 
|  | { | 
|  | logger(LOGGER_SQL, "$query\n"); | 
|  | logger(LOGGER_OK, "no commits found >= $p/$type/$plugin/ $vanityname\n"); | 
|  | } | 
|  | else | 
|  | { | 
|  | $plugtext = $plugin; | 
|  | if ($html) | 
|  | { | 
|  | $query = "SELECT MIN(`date`) FROM `cvsfiles` NATURAL JOIN `commits` WHERE `project` = '$proj' AND `branch` = '$branch' AND `cvsname` LIKE '/cvsroot/modeling/$p/$type/$plugin/%' AND `date` >= COALESCE(" . join(", ", $lastbuild) . ")"; | 
|  | $result2 = wmysql_query($query); | 
|  | logger(LOGGER_SQL, "$query\n"); | 
|  |  | 
|  | $row2 = mysql_fetch_row($result2); | 
|  | $plugtext = "<a href=\"http://www.eclipse.org/modeling/emf/searchcvs.php?q=" . urlencode("file: $p/$type/$plugin/ startdate: $row2[0] branch: $branch") . "\">$plugin</a>"; | 
|  | } | 
|  | $msg = mysql_num_rows($result) . " commit(s) found >= $plugtext $lastversion, currently at $vanityname\n"; | 
|  | while ($row = mysql_fetch_row($result)) | 
|  | { | 
|  | $msg .= "     ref: http://www.eclipse.org/modeling/emf/searchcvs.php?q="; | 
|  | if ($row[0]) | 
|  | { | 
|  | $msg .= "$row[0]\n"; | 
|  | } | 
|  | else | 
|  | { | 
|  | $msg .= urlencode("file: $row[1] startdate: $row[2] enddate: $row[2]") . "\n"; | 
|  | } | 
|  | } | 
|  | if ($version > convert_version($lastversion)) | 
|  | { | 
|  | logger(LOGGER_OK, $msg); | 
|  | } | 
|  | else | 
|  | { | 
|  | $msg .= "     --> $plugin must be > $vanityname\n"; | 
|  | logger(LOGGER_FAIL, $msg); | 
|  | $fails++; | 
|  | } | 
|  | } | 
|  |  | 
|  | while (sizeof($queue) > 0) | 
|  | { | 
|  | foreach (array_keys($queue) as $z) | 
|  | { | 
|  | $actual = preg_replace("/-feature$/", "", $queue[$z]); | 
|  | $fails += depgrep($dir, $actual, $vanityname, $plugin); | 
|  | unset($queue[$z]); | 
|  | } | 
|  | } | 
|  |  | 
|  | $f = "doc/$proj.doc-feature/feature.xml"; | 
|  | if (is_file("$dir/$f")) | 
|  | { | 
|  | $deps["$proj.doc-feature"] = "$dir/$f"; | 
|  | } | 
|  | else | 
|  | { | 
|  | logger(LOGGER_INFO, "couldn't find $f\n"); | 
|  | } | 
|  |  | 
|  | foreach (array_keys($deps) as $z) | 
|  | { | 
|  | $versions[$z] = convert_version(feature_version($deps[$z])); | 
|  | $lastversions[$z] = convert_version(feature_version(preg_replace("#cvssrc(?:_branches)?(/" . basename($dir) . ")#", "cvssrc_branches$1-latest", $deps[$z]))); | 
|  | } | 
|  | //print_r($deps); | 
|  |  | 
|  | $f = "doc/$proj.doc/build.xml"; | 
|  | if (is_file("$dir/$f")) | 
|  | { | 
|  | $versions["$proj.doc"] = doc_version($dir, $proj); | 
|  | $lastversions["$proj.doc"] = doc_version($lastdir, $proj); | 
|  | } | 
|  | else | 
|  | { | 
|  | logger(LOGGER_INFO, "couldn't find $f\n"); | 
|  | } | 
|  |  | 
|  | if ($version > convert_version($lastversion)) | 
|  | { | 
|  | foreach (array_keys($versions) as $z) | 
|  | { | 
|  | if (preg_match("/\.doc(?:-feature)?$/", $z) && $version - $versions[$z] <= 999) | 
|  | { | 
|  | $v1 = $vcache[$versions[$z]]; | 
|  | $v2 = $vcache[$version]; | 
|  | logger(LOGGER_OK, "$z (" . preg_replace("/\.\d+$/", "", $v1) . ") >= $plugin (" . preg_replace("/\.\d+$/", "", $v2) . "), ignoring service versions (actual versions were $v1 and $v2, respectively)\n"); | 
|  | } | 
|  | else if ($versions[$z] > $lastversions[$z]) | 
|  | { | 
|  | if ($versions[$z] - $lastversions[$z] == 1 || $versions[$z] - $lastversions[$z] == 1000) | 
|  | { | 
|  | logger(LOGGER_OK, "$z last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . "\n"); | 
|  | } | 
|  | else if (!isset($vcache[$lastversions[$z]]) || !$vcache[$lastversions[$z]]) // if feature is new, no previous version will exist | 
|  | { | 
|  | logger(LOGGER_INFO, "$z appears to be new, skipping checks\n"); | 
|  | } | 
|  | else | 
|  | { | 
|  | $msg = "$z last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . "\n"; | 
|  | $msg .= "     --> $z must be incremented only once per release cycle (last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . ")\n"; | 
|  | logger(LOGGER_FAIL, $msg); | 
|  | $fails++; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | $msg = "$z last released at " . $vcache[$lastversions[$z]] . ", currently at " . $vcache[$versions[$z]] . ", but $plugin has been incremented\n"; | 
|  | $msg .= "     --> $z must be > " . $vcache[$versions[$z]] . "\n"; | 
|  | logger(LOGGER_FAIL, $msg); | 
|  | $fails++; | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | logger(LOGGER_OK, "$plugin last released at $lastversion, currently at $vcache[$version]\n"); | 
|  | } | 
|  |  | 
|  | if ($fails == 0) | 
|  | { | 
|  | logger(LOGGER_OK, "$plugdir appears to be fine\n\n"); | 
|  | } | 
|  | else if ($verbosity >= LOGGER_OK) | 
|  | { | 
|  | print "\n"; | 
|  | } | 
|  | } | 
|  | else if (!preg_match("/-feature$/", $plugdir)) | 
|  | { | 
|  | logger(LOGGER_INFO, "couldn't find a MANIFEST.MF for $plugdir\n"); | 
|  | } | 
|  | $branchfails += $fails; | 
|  | } | 
|  |  | 
|  | /* we don't use the logger() interface here because we always want these to show, and we don't want a prepended tag */ | 
|  | if ($branchfails == 0) | 
|  | { | 
|  | print "$branch: ok\n\n"; | 
|  | } | 
|  | else | 
|  | { | 
|  | print "$branch: $branchfails failure(s): commit plugin/feature fixes to CVS, then run http://build.eclipse.org/modeling/build/updateSearchCVS.php to refresh database.\n" . | 
|  | "         Or, if you recently released an R build, update the searchcvs/cvssrc_branches/*-latest folder to the newer tag.\n"; | 
|  | ksort($issues); | 
|  | print join("\n", array_keys($issues)) . "\n\n"; | 
|  | $issues = array(); | 
|  | } | 
|  | } | 
|  |  | 
|  | $content = ob_get_contents(); | 
|  | ob_end_clean(); | 
|  |  | 
|  | if ($html) | 
|  | { | 
|  | print "<pre>\n"; | 
|  | print preg_replace("#(?<!href=\")(https?://[^ \n\t]+)#", "<a href=\"$1\">$1</a>", $content); | 
|  | print "</pre>\n"; | 
|  | print "<p> </p>\n"; | 
|  | print '<p align="right"><i><small><a href="http://wiki.eclipse.org/Plugin_Version_Auditing">about this tool</a></small></i></p>'."\n"; | 
|  | } | 
|  | else | 
|  | { | 
|  | print $content; | 
|  | } | 
|  |  | 
|  | /* find features which include or depend on the given plugin within the given directory */ | 
|  | function depgrep($dir, $plugin, $vanityname, $origplugin) | 
|  | { | 
|  | global $deps, $checked, $queue; | 
|  |  | 
|  | foreach (glob("$dir/{plugins,features,tests,examples}/*-feature/{,org.eclipse.*.sdk/}feature.xml", GLOB_BRACE) as $z) | 
|  | { | 
|  | $regs = null; | 
|  | if (preg_match("/<(?:includes|plugin)[^>]+id=\"\Q$plugin\E\"[^>]+version=\"([^\"]+)\"/s", file_get_contents($z), $regs)) | 
|  | { | 
|  | if ($plugin === $origplugin) | 
|  | { | 
|  | if ($vanityname !== $regs[1] && $regs[1] !== "0.0.0") | 
|  | { | 
|  | $msg = "$plugin is at $vanityname, but $z wants $regs[1]\n"; | 
|  | $msg .= "     --> $plugin must be $vanityname in $z\n"; | 
|  | logger(LOGGER_FAIL, $msg); | 
|  | } | 
|  | else | 
|  | { | 
|  | logger(LOGGER_OK, "$z wants $regs[1]\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | $m = null; | 
|  | if (preg_match("#/([^/]+)/feature\.xml$#", $z, $m)) | 
|  | { | 
|  | $deps[$m[1]] = $z; | 
|  | if (!isset($checked[$m[1]])) | 
|  | { | 
|  | $checked[$m[1]] = true; | 
|  | $queue[] = $m[1]; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /* parse the feature version out of a feature.xml file */ | 
|  | function feature_version($file) | 
|  | { | 
|  | $m = null; | 
|  | if (is_file($file) && is_readable($file)) | 
|  | { | 
|  | if (preg_match("/<feature[^>]+version=\"([^\"]+)\"/s", file_get_contents($file), $m)) | 
|  | { | 
|  | return $m[1]; | 
|  | } | 
|  | } | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /* parse the bundle version out of a MANIFEST.MF file (or plugin.xml if we can't find a MANIFEST.MF) */ | 
|  | function plugin_version($plugdir) | 
|  | { | 
|  | $ret = false; | 
|  |  | 
|  | $m = null; | 
|  | if (is_file("$plugdir/META-INF/MANIFEST.MF")) | 
|  | { | 
|  | if (preg_match("#^Bundle-Version:\s+(.+)$#m", file_get_contents("$plugdir/META-INF/MANIFEST.MF"), $m)) | 
|  | { | 
|  | $ret = $m[1]; | 
|  | } | 
|  | } | 
|  | else if (is_file("$plugdir/plugin.xml")) | 
|  | { | 
|  | if (preg_match("#<plugin[^>]+version\s*=\s*\"([^\"]+)\"[^>]+>#s", file_get_contents("$plugdir/plugin.xml"), $m)) | 
|  | { | 
|  | $ret = $m[1]; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | logger(LOGGER_FAIL, "couldn't find a MANIFEST.MF or plugin.xml for $plugdir!\n"); | 
|  | } | 
|  |  | 
|  | return $ret; | 
|  | } | 
|  |  | 
|  | /* convert a version number like 2.3.0.qualifier to 2*10^6 + 3*10^3 + 0, so they can be compared numerically | 
|  | * also associate the two values in $vcache so the operation can be reversed easily */ | 
|  | function convert_version($version) | 
|  | { | 
|  | global $vcache; | 
|  | $num = null; | 
|  | if ($version) | 
|  | { | 
|  | $version = preg_replace("/\.qualifier$/", "", $version); | 
|  | list($major, $minor, $patch) = split("\.", $version); | 
|  |  | 
|  | $num = ($major * pow(10, 6) + $minor * pow(10, 3) + $patch); | 
|  | $vcache[$num] = $version; | 
|  | } | 
|  | return $num; | 
|  | } | 
|  |  | 
|  | /* we need to check 2 different files for the doc plugin, so we'll just add it's version directly */ | 
|  | function doc_version($dir, $proj) | 
|  | { | 
|  | global $fail; | 
|  |  | 
|  | $v1 = null; | 
|  | $v2 = null; | 
|  |  | 
|  | $m = null; | 
|  | if (preg_match("#<property\s+name=\"pluginVersion\"\s+value=\"([^\"]+)\"\s*/>#", file_get_contents("$dir/doc/$proj.doc/build.xml"), $m)) | 
|  | { | 
|  | $v1 = $m[1]; | 
|  | } | 
|  |  | 
|  | $v2 = plugin_version("$dir/doc/$proj.doc"); | 
|  |  | 
|  | if ($v1 !== null && $v2 !== null) | 
|  | { | 
|  | if ("$v1.qualifier" === $v2 || $v1 === $v2) | 
|  | { | 
|  | return convert_version($v2); | 
|  | } | 
|  | else | 
|  | { | 
|  | $msg = "$dir/doc/$proj.doc is not internally consistent! ($v1 != $v2)\n"; | 
|  | $msg .= "     --> the versions in build.xml (currently $v1) and META-INF/MANIFEST.MF (currently $v2) must match\n"; | 
|  | logger(LOGGER_FAIL, $msg); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | $msg = "couldn't find all version information for $dir/doc/$proj.doc!\n"; | 
|  | if ($v1 === null) | 
|  | { | 
|  | $msg .= "     --> build.xml must have a <property name=\"pluginVersion\" value=\"x.x.x\"/>\n"; | 
|  | } | 
|  | if ($v2 === null) | 
|  | { | 
|  | $msg .= "     --> META-INF/MANIFEST.MF must have a Bundle-Version: x.x.x\n"; | 
|  | } | 
|  | logger(LOGGER_FAIL, $msg); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* wrapper for output, prepends a label and controls output based on the verbosity level */ | 
|  | function logger($type, $msg) | 
|  | { | 
|  | global $verbosity, $issues; | 
|  |  | 
|  | $labels = array( | 
|  | LOGGER_FAIL => "[ fail ]", | 
|  | LOGGER_OK =>   "[  ok  ]", | 
|  | LOGGER_INFO => "[ info ]", | 
|  | LOGGER_SQL =>  "[  sql ]", | 
|  | ); | 
|  |  | 
|  | if ($type <= $verbosity) | 
|  | { | 
|  | print $labels[$type] . " $msg"; | 
|  | $m = null; | 
|  | if (preg_match_all("/^(     -->.+)$/m", $msg, $m)) | 
|  | { | 
|  | foreach ($m[1] as $z) | 
|  | { | 
|  | $issues[$z] = true; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | ?> |