Future process()

in app/lib/dartdoc/dartdoc_runner.dart [196:427]


  Future<JobStatus> process(Job job) async {
    final packageStatus = await scoreCardBackend.getPackageStatus(
        job.packageName!, job.packageVersion!);
    // In case the package was deleted between scheduling and the actual delete.
    if (!packageStatus.exists) {
      _logger.info('Package does not exist: $job.');
      return JobStatus.skipped;
    }

    // We know that dartdoc will fail on this package, no reason to run it.
    if (packageStatus.isLegacy) {
      _logger.info('Package is on legacy SDK: $job.');
      await _storeScoreCard(
          job,
          _emptyReport(
            title: 'Did not run dartdoc',
            description:
                'This package is not compatible with the current analysis SDK version',
          ));
      return JobStatus.skipped;
    }

    // Do not check for discontinued status, we still generate documentation for
    // such packages.

    if (packageStatus.isObsolete) {
      _logger
          .info('Package is older than two years and has newer release: $job.');
      await _storeScoreCard(
          job,
          _emptyReport(
            title: 'Did not run dartdoc',
            description:
                'Package version is older than two years and has a newer release.',
          ));
      return JobStatus.skipped;
    }

    // Detect silent errors or timeouts and make sure we unblock the seemingly
    // neverending 'awaiting' statuses.
    if ((job.attemptCount ?? 0) > 1) {
      final currentCard = await scoreCardBackend.getScoreCardData(
        job.packageName!,
        job.packageVersion!,
        onlyCurrent: true,
      );
      if (currentCard?.dartdocReport == null) {
        // These package versions are worth investigating, but we don't need
        // alerts on them.
        _logger.warning('Missing dartdoc report when error was present: $job.');
        await _storeScoreCard(
            job,
            _emptyReport(
              title: 'Failed to run dartdoc',
              description: 'The dartdoc process timed out.',
            ));
      }
    }

    final logger =
        Logger('pub.dartdoc.runner/${job.packageName}/${job.packageVersion}');
    final tempDir =
        await Directory.systemTemp.createTemp('pub-dartlang-dartdoc');
    final tempDirPath = tempDir.resolveSymbolicLinksSync();
    final pkgPath = p.join(tempDirPath, 'pkg');
    final tarBaseDir = p.join(tempDirPath, 'output');
    final dartdocContentDir =
        p.join(tarBaseDir, job.packageName, job.packageVersion);
    final uploadDir = p.join(tempDirPath, 'upload');

    // directories need to be created
    await Directory(pkgPath).create(recursive: true);
    await Directory(dartdocContentDir).create(recursive: true);
    await Directory(uploadDir).create(recursive: true);

    final latestVersion =
        await packageBackend.getLatestVersion(job.packageName!);
    final bool isLatestStable = latestVersion == job.packageVersion;
    bool depsResolved = false;
    DartdocResult? dartdocResult;
    bool hasContent = false;
    PubDartdocData? dartdocData;

    String reportStatus = ReportStatus.failed;
    String? abortMessage;
    DartdocEntry? entry;
    try {
      await withToolEnv(
        usesPreviewSdk: packageStatus.usesPreviewAnalysisSdk,
        fn: (toolEnv) async {
          final sw = Stopwatch()..start();
          final usesFlutter = packageStatus.usesFlutter;

          final logFileOutput = StringBuffer();
          logFileOutput.write('Dartdoc generation for $job\n\n'
              'runtime: ${versions.runtimeVersion}\n'
              'runtime Dart SDK: ${versions.runtimeSdkVersion}\n'
              'dartdoc: ${versions.dartdocVersion}\n'
              'pana: ${versions.panaVersion}\n'
              'toolEnv Dart SDK: ${toolEnv.runtimeInfo.sdkVersion}\n'
              'usesFlutter: $usesFlutter\n'
              'flutter: ${toolEnv.runtimeInfo.flutterVersions}\n'
              'started: ${clock.now().toUtc().toIso8601String()}\n\n');

          await _runner.downloadAndExtract(
            package: job.packageName!,
            version: job.packageVersion!,
            destination: pkgPath,
          );
          final uuid = createUuid();

          // Resolve dependencies only for non-legacy package versions.
          if (!packageStatus.isLegacy) {
            final output = await _resolveDependencies(
                logger, toolEnv, job, pkgPath, usesFlutter, logFileOutput);
            depsResolved = output == null;
            abortMessage ??= output;
          } else {
            logFileOutput.write(
                'Package version does not allow current SDK, skipping pub upgrade.\n\n');
          }

          // Generate docs only for packages that have healthy dependencies.
          if (depsResolved) {
            dartdocResult = await _generateDocs(
                toolEnv, logger, job, pkgPath, dartdocContentDir, logFileOutput,
                usesPreviewSdk: packageStatus.usesPreviewAnalysisSdk);
            hasContent =
                dartdocResult!.hasIndexHtml && dartdocResult!.hasIndexJson;
          } else {
            logFileOutput
                .write('Dependencies were not resolved, skipping dartdoc.\n\n');
          }

          if (hasContent) {
            try {
              await DartdocCustomizer(customizerConfig(
                packageName: job.packageName!,
                packageVersion: job.packageVersion!,
                isLatestStable: job.isLatestStable,
              )).customizeDir(dartdocContentDir);
              logFileOutput.write('Content customization completed.\n\n');
            } catch (e, st) {
              // Do not block on customization failure.
              _logger.severe('Dartdoc customization failed ($job).', e, st);
              logFileOutput.write('Content customization failed.\n\n');
            }

            await _blob(uuid, dartdocContentDir, uploadDir, logFileOutput);
            await _tar(tempDirPath, tarBaseDir, uploadDir, logFileOutput);
          } else {
            logFileOutput.write('No content found!\n\n');
          }

          entry = await _createEntry(uuid, toolEnv, job, dartdocContentDir,
              uploadDir, usesFlutter, depsResolved, hasContent, sw.elapsed);
          logFileOutput
              .write('entry created: ${entry!.uuid} in ${sw.elapsed}\n\n');

          logFileOutput
              .write('completed: ${entry!.timestamp!.toIso8601String()}\n');
          await _writeLog(uploadDir, logFileOutput);
        },
      );

      final oldEntry =
          await dartdocBackend.getEntry(job.packageName!, job.packageVersion!);
      if (entry!.isRegression(oldEntry)) {
        logger.severe('Regression detected in $job, aborting upload.');
        // If `isLatest` has changed, we still want to update the old entry,
        // even if the job failed. `isLatest` is used to redirect latest
        // versions from versioned url to url with `/latest/`, and this
        // is cheaper than checking it on each request.
        if (oldEntry!.isLatest != entry!.isLatest) {
          await dartdocBackend.updateOldIsLatest(oldEntry,
              isLatest: entry!.isLatest);
        }
      } else {
        await dartdocBackend.uploadDir(entry!, uploadDir);
        reportStatus = hasContent ? ReportStatus.success : ReportStatus.failed;
      }

      if (!hasContent && isLatestStable) {
        reportIssueWithLatest(job, 'No content.');
      }

      dartdocData = await _loadPubDartdocData(logger, dartdocContentDir);
    } catch (e, st) {
      reportStatus = ReportStatus.aborted;
      if (isLatestStable) {
        reportIssueWithLatest(job, '$e\n$st');
      }
      abortMessage ??=
          'Running `dartdoc` failed with the following error: `$e`\n\n```\n$st\n```\n';
    } finally {
      await tempDir.delete(recursive: true);
    }

    final coverage = dartdocData?.coverage;
    ReportSection documentationSection;
    if (hasContent && coverage != null) {
      documentationSection = documentationCoverageSection(
        documented: coverage.documented,
        total: coverage.total,
      );
    } else {
      if (dartdocResult != null && dartdocResult!.wasTimeout) {
        abortMessage ??= '`dartdoc` timed out.';
      }
      if (abortMessage == null && dartdocResult != null) {
        final output =
            _mergeOutput(dartdocResult!.processResult, compress: true);
        abortMessage = '`dartdoc` failed with:\n\n```\n$output\n```';
      }
      abortMessage ??= '`dartdoc` failed with unknown reason.';
      documentationSection = dartdocFailedSection(abortMessage!);
    }
    await _storeScoreCard(
        job,
        DartdocReport(
          timestamp: clock.now().toUtc(),
          reportStatus: reportStatus,
          dartdocEntry: entry,
          documentationSection: documentationSection,
        ));

    if (abortMessage != null) {
      return JobStatus.aborted;
    } else {
      return hasContent ? JobStatus.success : JobStatus.failed;
    }
  }