Skip to content

Commit c7fd737

Browse files
authored
Output log files as collapsed sections in CI (#1556)
1 parent a5528f3 commit c7fd737

File tree

8 files changed

+189
-51
lines changed

8 files changed

+189
-51
lines changed

include/vcpkg/base/contractual-constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ namespace vcpkg
348348
inline constexpr StringLiteral FileInclude = "include";
349349
inline constexpr StringLiteral FileIncomplete = "incomplete";
350350
inline constexpr StringLiteral FileInfo = "info";
351+
inline constexpr StringLiteral FileIssueBodyMD = "issue_body.md";
351352
inline constexpr StringLiteral FileLicense = "LICENSE";
352353
inline constexpr StringLiteral FileLicenseDotTxt = "LICENSE.txt";
353354
inline constexpr StringLiteral FilePortfileDotCMake = "portfile.cmake";

include/vcpkg/commands.build.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,18 @@ namespace vcpkg
9494
StringLiteral to_string_locale_invariant(const BuildResult build_result);
9595
LocalizedString to_string(const BuildResult build_result);
9696
LocalizedString create_user_troubleshooting_message(const InstallPlanAction& action,
97+
CIKind detected_ci,
9798
const VcpkgPaths& paths,
98-
const Optional<Path>& issue_body);
99+
const std::vector<std::string>& error_logs,
100+
const Optional<Path>& maybe_issue_body);
99101
inline void print_user_troubleshooting_message(const InstallPlanAction& action,
102+
CIKind detected_ci,
100103
const VcpkgPaths& paths,
101-
Optional<Path>&& issue_body)
104+
const std::vector<std::string>& error_logs,
105+
Optional<Path>&& maybe_issue_body)
102106
{
103-
msg::println(Color::error, create_user_troubleshooting_message(action, paths, issue_body));
107+
msg::println(Color::error,
108+
create_user_troubleshooting_message(action, detected_ci, paths, error_logs, maybe_issue_body));
104109
}
105110

106111
/// <summary>

include/vcpkg/fwd/vcpkgcmdarguments.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,20 @@ namespace vcpkg
2020
struct VcpkgCmdArguments;
2121
struct FeatureFlagSettings;
2222
struct PortApplicableSetting;
23+
24+
enum class CIKind
25+
{
26+
None,
27+
GithubActions,
28+
GitLabCI,
29+
AzurePipelines,
30+
AppVeyor,
31+
AwsCodeBuild,
32+
CircleCI,
33+
HerokuCI,
34+
JenkinsCI,
35+
TeamCityCI,
36+
TravisCI,
37+
Generic
38+
};
2339
}

include/vcpkg/vcpkgcmdarguments.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,8 @@ namespace vcpkg
298298
f.dependency_graph = dependency_graph_enabled();
299299
return f;
300300
}
301-
const Optional<StringLiteral>& detected_ci_environment() const { return m_detected_ci_environment; }
301+
const Optional<StringLiteral>& detected_ci_environment_name() const { return m_detected_ci_environment_name; }
302+
CIKind detected_ci() const { return m_detected_ci_environment_type; }
302303

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

@@ -333,7 +334,8 @@ namespace vcpkg
333334

334335
std::string command;
335336

336-
Optional<StringLiteral> m_detected_ci_environment;
337+
Optional<StringLiteral> m_detected_ci_environment_name;
338+
CIKind m_detected_ci_environment_type;
337339

338340
friend LocalizedString usage_for_command(const CommandMetadata& command_metadata);
339341
CmdParser parser;

src/vcpkg/commands.build.cpp

Lines changed: 119 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ namespace vcpkg
207207
msg::print(Color::warning, warnings);
208208
}
209209
msg::println_error(create_error_message(result, spec));
210-
msg::print(create_user_troubleshooting_message(*action, paths, nullopt));
210+
msg::print(create_user_troubleshooting_message(*action, args.detected_ci(), paths, {}, nullopt));
211211
return 1;
212212
}
213213
case BuildResult::Excluded:
@@ -1694,43 +1694,144 @@ namespace vcpkg
16941694
return "https://github.com/microsoft/vcpkg/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+" + spec_name;
16951695
}
16961696

1697-
static std::string make_gh_issue_open_url(StringView spec_name, StringView triplet, StringView path)
1697+
static std::string make_gh_issue_open_url(StringView spec_name, StringView triplet, StringView body)
16981698
{
16991699
return Strings::concat("https://github.com/microsoft/vcpkg/issues/new?title=[",
17001700
spec_name,
17011701
"]+Build+error+on+",
17021702
triplet,
1703-
"&body=Copy+issue+body+from+",
1704-
Strings::percent_encode(path));
1703+
"&body=",
1704+
Strings::percent_encode(body));
1705+
}
1706+
1707+
static bool is_collapsible_ci_kind(CIKind kind)
1708+
{
1709+
switch (kind)
1710+
{
1711+
case CIKind::GithubActions:
1712+
case CIKind::GitLabCI:
1713+
case CIKind::AzurePipelines: return true;
1714+
case CIKind::None:
1715+
case CIKind::AppVeyor:
1716+
case CIKind::AwsCodeBuild:
1717+
case CIKind::CircleCI:
1718+
case CIKind::HerokuCI:
1719+
case CIKind::JenkinsCI:
1720+
case CIKind::TeamCityCI:
1721+
case CIKind::TravisCI:
1722+
case CIKind::Generic: return false;
1723+
default: Checks::unreachable(VCPKG_LINE_INFO);
1724+
}
1725+
}
1726+
1727+
static void append_file_collapsible(LocalizedString& output,
1728+
CIKind kind,
1729+
const ReadOnlyFilesystem& fs,
1730+
const Path& file)
1731+
{
1732+
auto title = file.filename();
1733+
auto contents = fs.read_contents(file, VCPKG_LINE_INFO);
1734+
switch (kind)
1735+
{
1736+
case CIKind::GithubActions:
1737+
// https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#grouping-log-lines
1738+
output.append_raw("::group::")
1739+
.append_raw(title)
1740+
.append_raw('\n')
1741+
.append_raw(contents)
1742+
.append_raw("::endgroup::\n");
1743+
break;
1744+
case CIKind::GitLabCI:
1745+
{
1746+
// https://docs.gitlab.com/ee/ci/jobs/job_logs.html#custom-collapsible-sections
1747+
using namespace std::chrono;
1748+
std::string section_name;
1749+
std::copy_if(title.begin(), title.end(), std::back_inserter(section_name), [](char c) {
1750+
return c == '.' || ParserBase::is_alphanum(c);
1751+
});
1752+
const auto timestamp = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
1753+
output
1754+
.append_raw(
1755+
fmt::format("\\e[0Ksection_start:{}:{}[collapsed=true]\r\\e[0K", timestamp, section_name))
1756+
.append_raw(title)
1757+
.append_raw('\n')
1758+
.append_raw(contents)
1759+
.append_raw(fmt::format("\\e[0Ksection_end:{}:{}\r\\e[0K\n", timestamp, section_name));
1760+
}
1761+
break;
1762+
case CIKind::AzurePipelines:
1763+
// https://learn.microsoft.com/en-us/azure/devops/pipelines/scripts/logging-commands?view=azure-devops&tabs=bash#formatting-commands
1764+
output.append_raw("##vso[task.uploadfile]")
1765+
.append_raw(file)
1766+
.append_raw('\n')
1767+
.append_raw("##[group]")
1768+
.append_raw(title)
1769+
.append_raw('\n')
1770+
.append_raw(contents)
1771+
.append_raw("##[endgroup]\n");
1772+
break;
1773+
case CIKind::None:
1774+
case CIKind::AppVeyor:
1775+
case CIKind::AwsCodeBuild:
1776+
case CIKind::CircleCI:
1777+
case CIKind::HerokuCI:
1778+
case CIKind::JenkinsCI:
1779+
case CIKind::TeamCityCI:
1780+
case CIKind::TravisCI:
1781+
case CIKind::Generic: Checks::unreachable(VCPKG_LINE_INFO, "CIKind not collapsible");
1782+
default: Checks::unreachable(VCPKG_LINE_INFO);
1783+
}
17051784
}
17061785

17071786
LocalizedString create_user_troubleshooting_message(const InstallPlanAction& action,
1787+
CIKind detected_ci,
17081788
const VcpkgPaths& paths,
1709-
const Optional<Path>& issue_body)
1789+
const std::vector<std::string>& error_logs,
1790+
const Optional<Path>& maybe_issue_body)
17101791
{
17111792
const auto& spec_name = action.spec.name();
17121793
const auto& triplet_name = action.spec.triplet().to_string();
17131794
LocalizedString result = msg::format(msgBuildTroubleshootingMessage1).append_raw('\n');
17141795
result.append_indent().append_raw(make_gh_issue_search_url(spec_name)).append_raw('\n');
1715-
result.append(msgBuildTroubleshootingMessage2).append_raw('\n');
1716-
if (issue_body.has_value())
1796+
result.append(msgBuildTroubleshootingMessage2).append_raw('\n').append_indent();
1797+
1798+
if (auto issue_body = maybe_issue_body.get())
17171799
{
1718-
const auto path = issue_body.get()->generic_u8string();
1719-
result.append_indent().append_raw(make_gh_issue_open_url(spec_name, triplet_name, path)).append_raw('\n');
1720-
if (!paths.get_filesystem().find_from_PATH("gh").empty())
1800+
auto& fs = paths.get_filesystem();
1801+
// The 'body' content is not localized because it becomes part of the posted GitHub issue
1802+
// rather than instructions for the current user of vcpkg.
1803+
if (is_collapsible_ci_kind(detected_ci))
17211804
{
1722-
Command gh("gh");
1723-
gh.string_arg("issue").string_arg("create").string_arg("-R").string_arg("microsoft/vcpkg");
1724-
gh.string_arg("--title").string_arg(fmt::format("[{}] Build failure on {}", spec_name, triplet_name));
1725-
gh.string_arg("--body-file").string_arg(path);
1726-
1727-
result.append(msgBuildTroubleshootingMessageGH).append_raw('\n');
1728-
result.append_indent().append_raw(gh.command_line());
1805+
auto body = fmt::format("Copy issue body from collapsed section \"{}\" in the ci log output",
1806+
issue_body->filename());
1807+
result.append_raw(make_gh_issue_open_url(spec_name, triplet_name, body)).append_raw('\n');
1808+
append_file_collapsible(result, detected_ci, fs, *issue_body);
1809+
for (Path error_log_path : error_logs)
1810+
{
1811+
append_file_collapsible(result, detected_ci, fs, error_log_path);
1812+
}
1813+
}
1814+
else
1815+
{
1816+
const auto path = issue_body->generic_u8string();
1817+
auto body = fmt::format("Copy issue body from {}", path);
1818+
result.append_raw(make_gh_issue_open_url(spec_name, triplet_name, body)).append_raw('\n');
1819+
auto gh_path = fs.find_from_PATH("gh");
1820+
if (!gh_path.empty())
1821+
{
1822+
Command gh(gh_path[0]);
1823+
gh.string_arg("issue").string_arg("create").string_arg("-R").string_arg("microsoft/vcpkg");
1824+
gh.string_arg("--title").string_arg(
1825+
fmt::format("[{}] Build failure on {}", spec_name, triplet_name));
1826+
gh.string_arg("--body-file").string_arg(path);
1827+
result.append(msgBuildTroubleshootingMessageGH).append_raw('\n');
1828+
result.append_indent().append_raw(gh.command_line());
1829+
}
17291830
}
17301831
}
17311832
else
17321833
{
1733-
result.append_indent()
1834+
result
17341835
.append_raw("https://github.com/microsoft/vcpkg/issues/"
17351836
"new?template=report-package-build-failure.md&title=[")
17361837
.append_raw(spec_name)

src/vcpkg/commands.install.cpp

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -603,14 +603,19 @@ namespace vcpkg
603603
if (result.code != BuildResult::Succeeded && build_options.keep_going == KeepGoing::No)
604604
{
605605
this_install.print_elapsed_time();
606-
print_user_troubleshooting_message(action, paths, result.stdoutlog.then([&](auto&) -> Optional<Path> {
607-
auto issue_body_path = paths.installed().root() / "vcpkg" / "issue_body.md";
608-
paths.get_filesystem().write_contents(
609-
issue_body_path,
610-
create_github_issue(args, result, paths, action, include_manifest_in_github_issue),
611-
VCPKG_LINE_INFO);
612-
return issue_body_path;
613-
}));
606+
print_user_troubleshooting_message(
607+
action,
608+
args.detected_ci(),
609+
paths,
610+
result.error_logs,
611+
result.stdoutlog.then([&](auto&) -> Optional<Path> {
612+
auto issue_body_path = paths.installed().root() / FileVcpkg / FileIssueBodyMD;
613+
paths.get_filesystem().write_contents(
614+
issue_body_path,
615+
create_github_issue(args, result, paths, action, include_manifest_in_github_issue),
616+
VCPKG_LINE_INFO);
617+
return issue_body_path;
618+
}));
614619
Checks::exit_fail(VCPKG_LINE_INFO);
615620
}
616621

src/vcpkg/commands.z-print-config.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ namespace vcpkg
4848
obj.insert(JsonIdHostTriplet, host_triplet.canonical_name());
4949
obj.insert(JsonIdVcpkgRoot, paths.root.native());
5050
obj.insert(JsonIdTools, paths.tools.native());
51-
if (auto ci_env = args.detected_ci_environment().get())
51+
if (auto ci_env = args.detected_ci_environment_name().get())
5252
{
5353
obj.insert(JsonIdDetectedCIEnvironment, *ci_env);
5454
}

src/vcpkg/vcpkgcmdarguments.cpp

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,55 +15,62 @@ namespace
1515
{
1616
using namespace vcpkg;
1717

18-
constexpr std::pair<StringLiteral, StringLiteral> KNOWN_CI_VARIABLES[]{
18+
struct CIRecord
19+
{
20+
StringLiteral env_var;
21+
StringLiteral name;
22+
CIKind type;
23+
};
24+
25+
constexpr CIRecord KNOWN_CI_VARIABLES[]{
1926
// Opt-out from CI detection
20-
{EnvironmentVariableVcpkgNoCi, "VCPKG_NO_CI"},
27+
{EnvironmentVariableVcpkgNoCi, "VCPKG_NO_CI", CIKind::None},
2128

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

2633
// AppVeyor
2734
// https://www.appveyor.com/docs/environment-variables/
28-
{EnvironmentVariableAppveyor, "AppVeyor"},
35+
{EnvironmentVariableAppveyor, "AppVeyor", CIKind::AppVeyor},
2936

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

3441
// CircleCI
3542
// https://circleci.com/docs/env-vars#built-in-environment-variables
36-
{EnvironmentVariableCircleCI, "Circle_CI"},
43+
{EnvironmentVariableCircleCI, "Circle_CI", CIKind::CircleCI},
3744

3845
// GitHub Actions
3946
// https://docs.github.com/en/actions/learn-github-actions/
40-
{EnvironmentVariableGitHubActions, "GitHub_Actions"},
47+
{EnvironmentVariableGitHubActions, "GitHub_Actions", CIKind::GithubActions},
4148

4249
// GitLab
4350
// https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
44-
{EnvironmentVariableGitLabCI, "GitLab_CI"},
51+
{EnvironmentVariableGitLabCI, "GitLab_CI", CIKind::GitLabCI},
4552

4653
// Heroku
4754
// https://devcenter.heroku.com/articles/heroku-ci#immutable-environment-variables
48-
{EnvironmentVariableHerokuTestRunId, "Heroku_CI"},
55+
{EnvironmentVariableHerokuTestRunId, "Heroku_CI", CIKind::HerokuCI},
4956

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

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

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

6370
// Generic CI environment variables
64-
{EnvironmentVariableCI, "Generic"},
65-
{EnvironmentVariableBuildId, "Generic"},
66-
{EnvironmentVariableBuildNumber, "Generic"},
71+
{EnvironmentVariableCI, "Generic", CIKind::Generic},
72+
{EnvironmentVariableBuildId, "Generic", CIKind::Generic},
73+
{EnvironmentVariableBuildNumber, "Generic", CIKind::Generic},
6774
};
6875

6976
constexpr StringLiteral KNOWN_CI_REPOSITORY_IDENTIFIERS[] = {
@@ -581,9 +588,10 @@ namespace vcpkg
581588
// detect whether we are running in a CI environment
582589
for (auto&& ci_env_var : KNOWN_CI_VARIABLES)
583590
{
584-
if (get_env(ci_env_var.first).has_value())
591+
if (get_env(ci_env_var.env_var).has_value())
585592
{
586-
m_detected_ci_environment = ci_env_var.second;
593+
m_detected_ci_environment_name = ci_env_var.name;
594+
m_detected_ci_environment_type = ci_env_var.type;
587595
break;
588596
}
589597
}
@@ -790,7 +798,7 @@ namespace vcpkg
790798
void VcpkgCmdArguments::track_environment_metrics() const
791799
{
792800
MetricsSubmission submission;
793-
if (auto ci_env = m_detected_ci_environment.get())
801+
if (auto ci_env = m_detected_ci_environment_name.get())
794802
{
795803
Debug::println("Detected CI environment: ", *ci_env);
796804
submission.track_string(StringMetric::DetectedCiEnvironment, *ci_env);

0 commit comments

Comments
 (0)