Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output log files as collapsed sections in ci #1556

Merged
merged 8 commits into from
Jan 17, 2025
Merged
1 change: 1 addition & 0 deletions include/vcpkg/base/contractual-constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ namespace vcpkg
inline constexpr StringLiteral FileInclude = "include";
inline constexpr StringLiteral FileIncomplete = "incomplete";
inline constexpr StringLiteral FileInfo = "info";
inline constexpr StringLiteral FileIssueBodyMD = "issue_body.md";
inline constexpr StringLiteral FileLicense = "LICENSE";
inline constexpr StringLiteral FileLicenseDotTxt = "LICENSE.txt";
inline constexpr StringLiteral FilePortfileDotCMake = "portfile.cmake";
Expand Down
5 changes: 5 additions & 0 deletions include/vcpkg/base/message-data.inc.h
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,11 @@ DECLARE_MESSAGE(ConstraintViolation, (), "", "Found a constraint violation:")
DECLARE_MESSAGE(ContinueCodeUnitInStart, (), "", "found continue code unit in start position")
DECLARE_MESSAGE(ControlCharacterInString, (), "", "Control character in string")
DECLARE_MESSAGE(ControlSupportsMustBeAPlatformExpression, (), "", "\"Supports\" must be a platform expression")
DECLARE_MESSAGE(CopyIssueBodyFromFile, (msg::path), "", "Copy issue body from {path}")
DECLARE_MESSAGE(CopyIssueBodyFromCollapsibleSection,
(msg::path),
"",
"Copy issue body from collapsed section \"{path}\" in the ci log output")
DECLARE_MESSAGE(CopyrightIsDir,
(),
"",
Expand Down
11 changes: 8 additions & 3 deletions include/vcpkg/commands.build.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,18 @@ namespace vcpkg
StringLiteral to_string_locale_invariant(const BuildResult build_result);
LocalizedString to_string(const BuildResult build_result);
LocalizedString create_user_troubleshooting_message(const InstallPlanAction& action,
CIKind detected_ci,
const VcpkgPaths& paths,
const Optional<Path>& issue_body);
const std::vector<std::string>& error_logs,
BillyONeal marked this conversation as resolved.
Show resolved Hide resolved
const Optional<Path>& maybe_issue_body);
inline void print_user_troubleshooting_message(const InstallPlanAction& action,
CIKind detected_ci,
const VcpkgPaths& paths,
Optional<Path>&& issue_body)
const std::vector<std::string>& error_logs,
Optional<Path>&& maybe_issue_body)
{
msg::println(Color::error, create_user_troubleshooting_message(action, paths, issue_body));
msg::println(Color::error,
create_user_troubleshooting_message(action, detected_ci, paths, error_logs, maybe_issue_body));
}

/// <summary>
Expand Down
16 changes: 16 additions & 0 deletions include/vcpkg/fwd/vcpkgcmdarguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,20 @@ namespace vcpkg
struct VcpkgCmdArguments;
struct FeatureFlagSettings;
struct PortApplicableSetting;

enum class CIKind
{
None,
GithubActions,
GitLabCI,
AzurePipelines,
AppVeyor,
AwsCodeBuild,
CircleCI,
HerokuCI,
JenkinsCI,
TeamCityCI,
TravisCI,
Generic
};
}
6 changes: 4 additions & 2 deletions include/vcpkg/vcpkgcmdarguments.h
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ namespace vcpkg
f.dependency_graph = dependency_graph_enabled();
return f;
}
const Optional<StringLiteral>& detected_ci_environment() const { return m_detected_ci_environment; }
const Optional<StringLiteral>& detected_ci_environment_name() const { return m_detected_ci_environment_name; }
CIKind detected_ci() const { return m_detected_ci_environment_type; }

const std::string& get_command() const noexcept { return command; }

Expand Down Expand Up @@ -333,7 +334,8 @@ namespace vcpkg

std::string command;

Optional<StringLiteral> m_detected_ci_environment;
Optional<StringLiteral> m_detected_ci_environment_name;
CIKind m_detected_ci_environment_type;

friend LocalizedString usage_for_command(const CommandMetadata& command_metadata);
CmdParser parser;
Expand Down
4 changes: 4 additions & 0 deletions locales/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,10 @@
"ContinueCodeUnitInStart": "found continue code unit in start position",
"ControlCharacterInString": "Control character in string",
"ControlSupportsMustBeAPlatformExpression": "\"Supports\" must be a platform expression",
"CopyIssueBodyFromCollapsibleSection": "Copy issue body from collapsed section \"{path}\" in the ci log output",
"_CopyIssueBodyFromCollapsibleSection.comment": "An example of {path} is /foo/bar.",
"CopyIssueBodyFromFile": "Copy issue body from {path}",
"_CopyIssueBodyFromFile.comment": "An example of {path} is /foo/bar.",
"CopyrightIsDir": "this port sets ${{CURRENT_PACKAGES_DIR}}/share/${{PORT}}/copyright to a directory, but it should be a file. Consider combining separate copyright files into one using vcpkg_install_copyright. To suppress this message, add set(VCPKG_POLICY_SKIP_COPYRIGHT_CHECK enabled)",
"CorruptedDatabase": "vcpkg's installation database corrupted. This is either a bug in vcpkg or something else has modified the contents of the 'installed' directory in an unexpected way. You may be able to fix this by deleting the 'installed' directory and reinstalling what you want to use. If this problem happens consistently, please file a bug at https://github.com/microsoft/vcpkg .",
"CorruptedInstallTree": "Your vcpkg 'installed' tree is corrupted.",
Expand Down
118 changes: 102 additions & 16 deletions src/vcpkg/commands.build.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@
msg::print(Color::warning, warnings);
}
msg::println_error(create_error_message(result, spec));
msg::print(create_user_troubleshooting_message(*action, paths, nullopt));
msg::print(create_user_troubleshooting_message(*action, args.detected_ci(), paths, {}, nullopt));
return 1;
}
case BuildResult::Excluded:
Expand Down Expand Up @@ -1692,38 +1692,124 @@
return "https://github.com/microsoft/vcpkg/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+" + spec_name;
}

static std::string make_gh_issue_open_url(StringView spec_name, StringView triplet, StringView path)
static std::string make_gh_issue_open_url(StringView spec_name, StringView triplet, StringView body)
{
return Strings::concat("https://github.com/microsoft/vcpkg/issues/new?title=[",
spec_name,
"]+Build+error+on+",
triplet,
"&body=Copy+issue+body+from+",
Strings::percent_encode(path));
"&body=",
Strings::percent_encode(body));
}

static bool is_collapsible_ci_kind(CIKind kind)
{
switch (kind)
{
case CIKind::GithubActions:
case CIKind::GitLabCI:
case CIKind::AzurePipelines: return true;
default: return false;
}
}

static void append_file_collapsible(LocalizedString& output,
CIKind kind,
const ReadOnlyFilesystem& fs,
const Path& file)
{
if (!is_collapsible_ci_kind(kind))
{
Checks::unreachable(VCPKG_LINE_INFO);
}

auto title = file.filename();
auto contents = fs.read_contents(file, VCPKG_LINE_INFO);
switch (kind)
{
case CIKind::GithubActions:

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘None’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘AppVeyor’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘AwsCodeBuild’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘CircleCI’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘HerokuCI’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘JenkinsCI’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘TeamCityCI’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘TravisCI’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (ubuntu-20.04, linux-ci)

enumeration value ‘Generic’ not handled in switch [-Werror=switch]

Check failure on line 1730 in src/vcpkg/commands.build.cpp

View workflow job for this annotation

GitHub Actions / builds / build (macos-13, macos-ci)

9 enumeration values not handled in switch: 'None', 'AppVeyor', 'AwsCodeBuild'... [-Werror,-Wswitch]
// https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#grouping-log-lines
output.append_raw("::group::")
.append_raw(title)
.append_raw('\n')
.append_raw(contents)
.append_raw("::endgroup::\n");
break;
case CIKind::GitLabCI:
{
// https://docs.gitlab.com/ee/ci/jobs/job_logs.html#custom-collapsible-sections
using namespace std::chrono;
std::string section_name;
std::copy_if(title.begin(), title.end(), std::back_inserter(section_name), [](char c) {
return c == '.' || ParserBase::is_alphanum(c);
});
const auto timestamp = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
output
.append_raw(
fmt::format("\\e[0Ksection_start:{}:{}[collapsed=true]\r\\e[0K", timestamp, section_name))
.append_raw(title)
.append_raw('\n')
.append_raw(contents)
.append_raw(fmt::format("\\e[0Ksection_end:{}:{}\r\\e[0K\n", timestamp, section_name));
}
break;
case CIKind::AzurePipelines:
// https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#formatting-commands
output.append_raw("##vso[task.uploadfile]")
.append_raw(file)
.append_raw('\n')
.append_raw("##[group]")
.append_raw(title)
.append_raw('\n')
.append_raw(contents)
.append_raw("##[endgroup]\n");
break;
}
}

LocalizedString create_user_troubleshooting_message(const InstallPlanAction& action,
CIKind detected_ci,
const VcpkgPaths& paths,
const Optional<Path>& issue_body)
const std::vector<std::string>& error_logs,
const Optional<Path>& maybe_issue_body)
{
const auto& spec_name = action.spec.name();
const auto& triplet_name = action.spec.triplet().to_string();
LocalizedString result = msg::format(msgBuildTroubleshootingMessage1).append_raw('\n');
result.append_indent().append_raw(make_gh_issue_search_url(spec_name)).append_raw('\n');
result.append(msgBuildTroubleshootingMessage2).append_raw('\n');
if (issue_body.has_value())

if (auto issue_body = maybe_issue_body.get())
{
const auto path = issue_body.get()->generic_u8string();
result.append_indent().append_raw(make_gh_issue_open_url(spec_name, triplet_name, path)).append_raw('\n');
if (!paths.get_filesystem().find_from_PATH("gh").empty())
auto& fs = paths.get_filesystem();
const auto path = issue_body->generic_u8string();
const bool collapsible = is_collapsible_ci_kind(detected_ci);
const auto body =
collapsible ? msg::format(msgCopyIssueBodyFromCollapsibleSection, msg::path = issue_body->filename())
: msg::format(msgCopyIssueBodyFromFile, msg::path = path);
BillyONeal marked this conversation as resolved.
Show resolved Hide resolved

result.append_indent().append_raw(make_gh_issue_open_url(spec_name, triplet_name, body)).append_raw('\n');
if (collapsible)
{
Command gh("gh");
gh.string_arg("issue").string_arg("create").string_arg("-R").string_arg("microsoft/vcpkg");
gh.string_arg("--title").string_arg(fmt::format("[{}] Build failure on {}", spec_name, triplet_name));
gh.string_arg("--body-file").string_arg(path);

result.append(msgBuildTroubleshootingMessageGH).append_raw('\n');
result.append_indent().append_raw(gh.command_line());
append_file_collapsible(result, detected_ci, fs, *issue_body);
for (Path error_log_path : error_logs)
{
append_file_collapsible(result, detected_ci, fs, error_log_path);
}
}
else
{
auto gh_path = fs.find_from_PATH("gh");
if (!gh_path.empty())
{
Command gh(gh_path[0]);
gh.string_arg("issue").string_arg("create").string_arg("-R").string_arg("microsoft/vcpkg");
gh.string_arg("--title").string_arg(
fmt::format("[{}] Build failure on {}", spec_name, triplet_name));
gh.string_arg("--body-file").string_arg(path);
result.append(msgBuildTroubleshootingMessageGH).append_raw('\n');
result.append_indent().append_raw(gh.command_line());
}
}
}
else
Expand Down
21 changes: 13 additions & 8 deletions src/vcpkg/commands.install.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -603,14 +603,19 @@ namespace vcpkg
if (result.code != BuildResult::Succeeded && build_options.keep_going == KeepGoing::No)
{
this_install.print_elapsed_time();
print_user_troubleshooting_message(action, paths, result.stdoutlog.then([&](auto&) -> Optional<Path> {
auto issue_body_path = paths.installed().root() / "vcpkg" / "issue_body.md";
paths.get_filesystem().write_contents(
issue_body_path,
create_github_issue(args, result, paths, action, include_manifest_in_github_issue),
VCPKG_LINE_INFO);
return issue_body_path;
}));
print_user_troubleshooting_message(
action,
args.detected_ci(),
paths,
result.error_logs,
result.stdoutlog.then([&](auto&) -> Optional<Path> {
auto issue_body_path = paths.installed().root() / FileVcpkg / FileIssueBodyMD;
paths.get_filesystem().write_contents(
issue_body_path,
create_github_issue(args, result, paths, action, include_manifest_in_github_issue),
VCPKG_LINE_INFO);
return issue_body_path;
}));
Checks::exit_fail(VCPKG_LINE_INFO);
}

Expand Down
2 changes: 1 addition & 1 deletion src/vcpkg/commands.z-print-config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ namespace vcpkg
obj.insert(JsonIdHostTriplet, host_triplet.canonical_name());
obj.insert(JsonIdVcpkgRoot, paths.root.native());
obj.insert(JsonIdTools, paths.tools.native());
if (auto ci_env = args.detected_ci_environment().get())
if (auto ci_env = args.detected_ci_environment_name().get())
{
obj.insert(JsonIdDetectedCIEnvironment, *ci_env);
}
Expand Down
46 changes: 27 additions & 19 deletions src/vcpkg/vcpkgcmdarguments.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,55 +15,62 @@ namespace
{
using namespace vcpkg;

constexpr std::pair<StringLiteral, StringLiteral> KNOWN_CI_VARIABLES[]{
struct CIRecord
{
StringLiteral env_var;
StringLiteral name;
CIKind type;
};

constexpr CIRecord KNOWN_CI_VARIABLES[]{
// Opt-out from CI detection
{EnvironmentVariableVcpkgNoCi, "VCPKG_NO_CI"},
{EnvironmentVariableVcpkgNoCi, "VCPKG_NO_CI", CIKind::None},

// Azure Pipelines
// https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables#system-variables
{EnvironmentVariableTfBuild, "Azure_Pipelines"},
{EnvironmentVariableTfBuild, "Azure_Pipelines", CIKind::AzurePipelines},

// AppVeyor
// https://www.appveyor.com/docs/environment-variables/
{EnvironmentVariableAppveyor, "AppVeyor"},
{EnvironmentVariableAppveyor, "AppVeyor", CIKind::AppVeyor},

// AWS Code Build
// https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html
{EnvironmentVariableCodebuildBuildId, "AWS_CodeBuild"},
{EnvironmentVariableCodebuildBuildId, "AWS_CodeBuild", CIKind::AwsCodeBuild},

// CircleCI
// https://circleci.com/docs/env-vars#built-in-environment-variables
{EnvironmentVariableCircleCI, "Circle_CI"},
{EnvironmentVariableCircleCI, "Circle_CI", CIKind::CircleCI},

// GitHub Actions
// https://docs.github.com/en/actions/learn-github-actions/
{EnvironmentVariableGitHubActions, "GitHub_Actions"},
{EnvironmentVariableGitHubActions, "GitHub_Actions", CIKind::GithubActions},

// GitLab
// https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
{EnvironmentVariableGitLabCI, "GitLab_CI"},
{EnvironmentVariableGitLabCI, "GitLab_CI", CIKind::GitLabCI},

// Heroku
// https://devcenter.heroku.com/articles/heroku-ci#immutable-environment-variables
{EnvironmentVariableHerokuTestRunId, "Heroku_CI"},
{EnvironmentVariableHerokuTestRunId, "Heroku_CI", CIKind::HerokuCI},

// Jenkins
// https://wiki.jenkins.io/display/JENKINS/Building+a+software+project#Buildingasoftwareproject-belowJenkinsSetEnvironmentVariables
{EnvironmentVariableJenkinsHome, "Jenkins_CI"},
{EnvironmentVariableJenkinsUrl, "Jenkins_CI"},
{EnvironmentVariableJenkinsHome, "Jenkins_CI", CIKind::JenkinsCI},
{EnvironmentVariableJenkinsUrl, "Jenkins_CI", CIKind::JenkinsCI},

// TeamCity
// https://www.jetbrains.com/help/teamcity/predefined-build-parameters.html#Predefined+Server+Build+Parameters
{EnvironmentVariableTeamcityVersion, "TeamCity_CI"},
{EnvironmentVariableTeamcityVersion, "TeamCity_CI", CIKind::TeamCityCI},

// Travis CI
// https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
{EnvironmentVariableTravis, "Travis_CI"},
{EnvironmentVariableTravis, "Travis_CI", CIKind::TravisCI},

// Generic CI environment variables
{EnvironmentVariableCI, "Generic"},
{EnvironmentVariableBuildId, "Generic"},
{EnvironmentVariableBuildNumber, "Generic"},
{EnvironmentVariableCI, "Generic", CIKind::Generic},
{EnvironmentVariableBuildId, "Generic", CIKind::Generic},
{EnvironmentVariableBuildNumber, "Generic", CIKind::Generic},
};

constexpr StringLiteral KNOWN_CI_REPOSITORY_IDENTIFIERS[] = {
Expand Down Expand Up @@ -581,9 +588,10 @@ namespace vcpkg
// detect whether we are running in a CI environment
for (auto&& ci_env_var : KNOWN_CI_VARIABLES)
{
if (get_env(ci_env_var.first).has_value())
if (get_env(ci_env_var.env_var).has_value())
{
m_detected_ci_environment = ci_env_var.second;
m_detected_ci_environment_name = ci_env_var.name;
m_detected_ci_environment_type = ci_env_var.type;
break;
}
}
Expand Down Expand Up @@ -790,7 +798,7 @@ namespace vcpkg
void VcpkgCmdArguments::track_environment_metrics() const
{
MetricsSubmission submission;
if (auto ci_env = m_detected_ci_environment.get())
if (auto ci_env = m_detected_ci_environment_name.get())
{
Debug::println("Detected CI environment: ", *ci_env);
submission.track_string(StringMetric::DetectedCiEnvironment, *ci_env);
Expand Down
Loading