2cb1f903afe83b27c2561fb2cff129d27878a46a lrnassar Thu Feb 12 15:23:10 2026 -0800 Adding a new flag, -allowWarnings, to hubCheck so that it doesn't exit out when it raises warnings. This way it doesn't break pipelines on common warning complaints like track desc pages, refs #30723 diff --git src/hg/utils/hubCheck/hubCheck.c src/hg/utils/hubCheck/hubCheck.c index 62a38e38f57..821e67e2d20 100644 --- src/hg/utils/hubCheck/hubCheck.c +++ src/hg/utils/hubCheck/hubCheck.c @@ -36,70 +36,73 @@ "options:\n" " -noTracks - don't check remote files for tracks, just trackDb (faster)\n" " -checkSettings - check trackDb settings to spec\n" " -version=[v?|url] - version to validate settings against\n" " (defaults to version in hub.txt, or current standard)\n" " -extra=[file|url] - accept settings in this file (or url)\n" " -level=base|required - reject settings below this support level\n" " -settings - just list settings with support level\n" " -genome=genome - only check this genome\n" " -udcDir=/dir/to/cache - place to put cache for remote bigBed/bigWigs.\n" " Will create this directory if not existing\n" " -httpsCertCheck=[abort,warn,log,none] - set the ssl certificate verification mode.\n" " -httpsCertCheckDomainExceptions= - space separated list of domains to whitelist.\n" " -printMeta - print the metadata for each track\n" " -cacheTime=N - set cache refresh time in seconds, default %d\n" + " -allowWarnings - return 0 exit code when only warnings are found (no errors)\n" " -verbose=2 - output verbosely\n" , cacheTime ); } static struct optionSpec options[] = { {"version", OPTION_STRING}, {"level", OPTION_STRING}, {"extra", OPTION_STRING}, {"noTracks", OPTION_BOOLEAN}, {"settings", OPTION_BOOLEAN}, {"checkSettings", OPTION_BOOLEAN}, {"genome", OPTION_STRING}, {"test", OPTION_BOOLEAN}, {"printMeta", OPTION_BOOLEAN}, {"udcDir", OPTION_STRING}, {"httpsCertCheck", OPTION_STRING}, {"httpsCertCheckDomainExceptions", OPTION_STRING}, {"specHost", OPTION_STRING}, {"cacheTime", OPTION_INT}, + {"allowWarnings", OPTION_BOOLEAN}, // intentionally undocumented option for hgHubConnect {"htmlOut", OPTION_BOOLEAN}, {NULL, 0}, }; struct trackHubCheckOptions /* How to check track hub */ { boolean checkFiles; /* check remote files exist and are correct type */ boolean checkSettings; /* check trackDb settings to spec */ boolean printMeta; /* print out the metadata for each track */ char *version; /* hub spec version to check */ char *specHost; /* server hosting hub spec */ char *level; /* check hub is valid to this support level */ char *extraFile; /* name of extra file/url with additional settings to accept */ char *genome; /* only check this genome */ /* intermediate data */ struct hash *settings; /* supported settings for this version */ struct hash *extra; /* additional trackDb settings to accept */ struct slName *suggest; /* list of supported settings for suggesting */ + boolean allowWarnings; /* return 0 exit code for warnings (no errors) */ /* hgHubConnect only */ boolean htmlOut; /* put special formatted text into the errors dyString */ }; struct trackHubSettingSpec /* Setting name and support level, from trackDbHub.html (the spec) */ { struct trackHubSettingSpec *next; char *name; /* setting name */ char *level; /* support level (required, base, full, new, deprecated) */ }; /* Mini English spell-check using axt sequence alignment code! From JK * Works in this context when thresholded high. */ @@ -697,32 +700,33 @@ // check that the subtrack does not have any bogus subgroups that don't exist in the parent for (i = 0; i < membership->count; i++) { char *subgroupName = membership->subgroups[i]; if (!subgroupingExists(tdb->parent, subgroupName)) { warn("subtrack \"%s\" declares subgroup \"%s\", but the parent composite does not define this subgroup. Check the 'subGroup' lines in the parent stanza for a matching group name.", subtrackName, subgroupName); } } } } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { - retVal = 1; trackDbErr(errors, errCatch->message->string, genome, tdb, options->htmlOut); + if (errCatch->gotError || !options->allowWarnings) + retVal = 1; } errCatchFree(&errCatch); return retVal; } int hubCheckCompositeSettings(struct trackHubGenome *genome, struct trackDb *tdb, struct dyString *errors, struct trackHubCheckOptions *options) /* Check composite level settings like subgroups, dimensions, sortOrder, etc. * Note that this function returns an int because we want to warn about all errors in a single * composite stanza rather than errAbort on the first error */ { int retVal = 0; // for now all the combination style stanzas can get checked here, but // in the future they might need their own routines @@ -737,30 +741,31 @@ (void)membersForAllSubGroupsGet(tdb, NULL); // check that multiWigs have > 1 track char *multiWigSetting = trackDbLocalSetting(tdb, "container"); if (multiWigSetting && slCount(tdb->subtracks) < 2) { errAbort("container multiWig %s has only one subtrack. multiWigs must have more than one subtrack", tdb->track); } } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { // don't add a new line because one will already be inserted by the errCatch->message trackDbErr(errors, errCatch->message->string, genome, tdb, options->htmlOut); + if (errCatch->gotError || !options->allowWarnings) retVal = 1; } return retVal; } void hubCheckParentsAndChildren(struct trackDb *tdb) /* Check that a single trackDb stanza has the correct parent and subtrack pointers */ { if (tdbIsSuper(tdb) || tdbIsComposite(tdb) || tdbIsCompositeView(tdb) || tdbIsContainer(tdb)) { if (tdb->subtracks == NULL) { errAbort("Track \"%s\" is declared superTrack, compositeTrack, view or " "container, but has no subtracks", tdb->track); @@ -978,30 +983,31 @@ if (errCatchStart(errCatch)) { if (tdb->errMessage) // if we found any errors when first reading in the trackDb errAbort("%s",tdb->errMessage); hubCheckParentsAndChildren(tdb); if (trackIsContainer) retVal |= hubCheckCompositeSettings(genome, tdb, errors, options); if (tdbIsSubtrack(tdb)) retVal |= hubCheckSubtrackSettings(genome, tdb, errors, options); // check that type line is syntactically correct regardless of // if we actually want to check the data file itself boolean foundTypeError = checkTypeLine(genome, tdb, errors, options); + retVal |= foundTypeError; // No point in checking the data file if the type setting is incorrect, // since hubCheckBigDataUrl will error out early with a less clear message // if the type line is messed up. This has the added benefit of providing // consistent messaging on command line interface vs web interface if (!foundTypeError && options->checkFiles) hubCheckBigDataUrl(hub, genome, tdb); checkViewLimitsSettings(tdb); if (!sameString(tdb->track, "cytoBandIdeo")) { trackHubAddDescription(genome->trackDbFile, tdb); if (!tdb->html) @@ -1012,32 +1018,33 @@ if (!trackIsContainer && sameString(trackDbRequiredSetting(tdb, "type"), "bigWig")) { char *autoScaleSetting = trackDbLocalSetting(tdb, "autoScale"); if (autoScaleSetting && !sameString(autoScaleSetting, "off") && !sameString(autoScaleSetting, "on")) { errAbort("track \"%s\" uses 'autoScale %s', but individual bigWig tracks only accept " "'autoScale on' or 'autoScale off'. To use 'autoScale %s', move that setting to the " "parent composite stanza instead.", trackHubSkipHubName(tdb->track), autoScaleSetting, autoScaleSetting); } } } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { - retVal = 1; trackDbErr(errors, errCatch->message->string, genome, tdb, options->htmlOut); + if (errCatch->gotError || !options->allowWarnings) + retVal = 1; if (errCatch->gotError) trackDbErrorCount += 1; } errCatchFree(&errCatch); if (options->htmlOut) { if (trackIsContainer) { for (tempTdb = tdb->subtracks; tempTdb != NULL; tempTdb = tempTdb->next) { char subtrackName[512]; safef(subtrackName, sizeof(subtrackName), "%s_%s", trackHubSkipHubName(genome->name), trackHubSkipHubName(tempTdb->track)); textName = trackHubSkipHubName(tempTdb->longLabel); dyStringPrintf(errors, "%s,", makeFolderObjectString(subtrackName, textName, idName, "TRACK", TRUE, retVal)); @@ -1086,31 +1093,31 @@ warn("warning: missing htmlPath setting for assembly hub '%s'", genome->name); else if (!extFileExists(htmlPath)) warn("warning: '%s' htmlPath file does not exist or is not accessible: '%s'", genome->name, htmlPath); } boolean foundFirstGenome = FALSE; tdbList = trackHubTracksForGenome(hub, genome, NULL, &foundFirstGenome); tdbList = trackDbLinkUpGenerations(tdbList); tdbList = trackDbPolishAfterLinkup(tdbList, genome->name); trackHubPolishTrackNames(hub, tdbList); } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { openedGenome = TRUE; genomeErr(errors, errCatch->message->string, hub, genome, options->htmlOut); - if (errCatch->gotError || errCatch->gotWarning) + if (errCatch->gotError || !options->allowWarnings) genomeErrorCount += 1; } errCatchFree(&errCatch); verbose(2, "%d tracks in %s\n", slCount(tdbList), genome->name); struct trackDb *tdb; int tdbCheckVal; static struct dyString *tdbDyString = NULL; if (!tdbDyString) tdbDyString = dyStringNew(0); // build up the track results list, keep track of number of errors, then // open up genomes folder char *genomeName = trackHubSkipHubName(genome->name); for (tdb = tdbList; tdb != NULL; tdb = tdb->next) @@ -1178,32 +1185,33 @@ struct dyString *hubErrors = dyStringNew(0); int retVal = 0; if (errCatchStart(errCatch)) { hub = trackHubOpen(hubUrl, ""); char *descUrl = hub->descriptionUrl; if (descUrl == NULL) warn("warning: missing hub overview description page. Add a 'descriptionUrl hubDescription.html' line to hub.txt."); else if (!extFileExists(descUrl)) warn("warning: descriptionUrl '%s' could not be accessed. Verify the file exists at the specified path and is publicly readable.", hub->descriptionUrl); } errCatchEnd(errCatch); if (errCatch->gotError || errCatch->gotWarning) { - retVal = 1; hubErr(hubErrors, errCatch->message->string, hub, options->htmlOut); + if (errCatch->gotError || !options->allowWarnings) + retVal = 1; if (options->htmlOut) { if (hub && hub->shortLabel) { dyStringPrintf(errors, "trackData['#'] = [%s,", makeFolderObjectString(hub->shortLabel, "Hub Errors", "#", "Click to open node", TRUE, TRUE)); } else { dyStringPrintf(errors, "trackData['#'] = [%s,", makeFolderObjectString("Hub Error", "Hub Errors", "#", "Click to open node", TRUE, TRUE)); } @@ -1313,30 +1321,31 @@ optionInit(&argc, argv, options); if (argc != 2 && !optionExists("settings")) usage(); struct trackHubCheckOptions *checkOptions = NULL; AllocVar(checkOptions); checkOptions->specHost = (optionExists("test") ? "genome-test.soe.ucsc.edu" : "genome.ucsc.edu"); checkOptions->specHost = optionVal("specHost", checkOptions->specHost); checkOptions->printMeta = optionExists("printMeta"); checkOptions->checkFiles = !optionExists("noTracks"); checkOptions->checkSettings = optionExists("checkSettings"); checkOptions->genome = optionVal("genome", NULL); +checkOptions->allowWarnings = optionExists("allowWarnings"); struct trackHubSettingSpec *setting = NULL; AllocVar(setting); setting->level = optionVal("level", "all"); if (trackHubSettingLevel(setting) < 0) { fprintf(stderr, "ERROR: Unrecognized support level %s\n\n", setting->level); usage(); } checkOptions->level = setting->level; char *version = NULL; if (optionExists("version")) version = optionVal("version", NULL); checkOptions->version = version; @@ -1375,35 +1384,33 @@ { setenv("https_cert_check_domain_exceptions", httpsCertCheckDomainExceptions, 1); } knetUdcInstall(); // make the htslib library use udc if (optionExists("settings")) { showSettings(checkOptions); return 0; } // hgHubConnect specific option for generating a jstree of the hub errors checkOptions->htmlOut = optionExists("htmlOut"); struct dyString *errors = dyStringNew(1024); -if (trackHubCheck(argv[1], checkOptions, errors) || checkOptions->htmlOut) - { - if (checkOptions->htmlOut) // just dump errors string to stdout +int checkResult = trackHubCheck(argv[1], checkOptions, errors); +if (checkOptions->htmlOut) { printf("%s", errors->string); - return 1; + return checkResult ? 1 : 0; } - else +else if (errors->stringSize > 0) { // uniquify and count errors struct slName *errs = slNameListFromString(errors->string, '\n'); slUniqify(&errs, slNameCmp, slNameFree); int errCount = slCount(errs); printf("Found %d problem%s:\n", errCount, errCount == 1 ? "" : "s"); printf("%s\n", slNameListToString(errs, '\n')); - return 1; - } + return checkResult ? 1 : 0; } return 0; }