mirror of
https://github.com/microsoft/vcpkg.git
synced 2024-12-27 18:31:15 +08:00
[vcpkg] Implement versions db generator (#13777)
* [vcpkg] Add script to generate ports versions history * [vcpkg] Fix formatting * Fetch port versions from commit ID * Use global --x-json switch * Use --no-checkout when cloning secondary instance * Clone from local repository instead of from GitHub * Use CmdLineBuilder to build git commands * Use CmdLineBuilder and reduce repeated code * Fetch version at baseline and code cleanup * Guess version scheme from old CONTROL files * Rename version db generator script * Simplify x-history json output * Use CONTROL/manifest parsers on x-history * Use git-tree instaed of commit-id * Remove 'ports' field from root object * Clean up code * More code cleanup * Improve port version detection * Improve generator logging * Do not ignore parsing errors in CONTROL files * PR review comments in Python script * Fix subprocess.run() calls * Make `canonicalize()` return error instead of terminating * [vcpkg] Add tests for new test_parse_control_file paths * Remove unnecessary std::move() calls * Fix formatting * Python formatting Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
This commit is contained in:
parent
ac2ddd5f05
commit
e9f8cc67a5
71
scripts/generatePortVersionsDb.py
Normal file
71
scripts/generatePortVersionsDb.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
from subprocess import CalledProcessError
|
||||||
|
from json.decoder import JSONDecodeError
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_git_ref():
|
||||||
|
output = subprocess.run(['git.exe', '-C', SCRIPT_DIRECTORY, 'rev-parse', '--verify', 'HEAD'],
|
||||||
|
capture_output=True,
|
||||||
|
encoding='utf-8')
|
||||||
|
if output.returncode == 0:
|
||||||
|
return output.stdout.strip()
|
||||||
|
print(f"Failed to get git ref:", output.stderr.strip(), file=sys.stderr)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_port_versions_db(ports_path, db_path, revision):
|
||||||
|
start_time = time.time()
|
||||||
|
port_names = [item for item in os.listdir(ports_path) if os.path.isdir(os.path.join(ports_path, item))]
|
||||||
|
total_count = len(port_names)
|
||||||
|
for counter, port_name in enumerate(port_names):
|
||||||
|
containing_dir = os.path.join(db_path, f'{port_name[0]}-')
|
||||||
|
os.makedirs(containing_dir, exist_ok=True)
|
||||||
|
output_filepath = os.path.join(containing_dir, f'{port_name}.json')
|
||||||
|
if not os.path.exists(output_filepath):
|
||||||
|
output = subprocess.run(
|
||||||
|
[os.path.join(SCRIPT_DIRECTORY, '../vcpkg'), 'x-history', port_name, '--x-json'],
|
||||||
|
capture_output=True, encoding='utf-8')
|
||||||
|
if output.returncode == 0:
|
||||||
|
try:
|
||||||
|
versions_object = json.loads(output.stdout)
|
||||||
|
with open(output_filepath, 'w') as output_file:
|
||||||
|
json.dump(versions_object, output_file)
|
||||||
|
except JSONDecodeError:
|
||||||
|
print(f'Maformed JSON from vcpkg x-history {port_name}: ', output.stdout.strip(), file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(f'x-history {port_name} failed: ', output.stdout.strip(), file=sys.stderr)
|
||||||
|
# This should be replaced by a progress bar
|
||||||
|
if counter > 0 and counter % 100 == 0:
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
print(f'Processed {counter} out of {total_count}. Elapsed time: {elapsed_time:.2f} seconds')
|
||||||
|
rev_file = os.path.join(db_path, revision)
|
||||||
|
Path(rev_file).touch()
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
print(f'Processed {total_count} total ports. Elapsed time: {elapsed_time:.2f} seconds')
|
||||||
|
|
||||||
|
|
||||||
|
def main(ports_path, db_path):
|
||||||
|
revision = get_current_git_ref()
|
||||||
|
if not revision:
|
||||||
|
print('Couldn\'t fetch current Git revision', file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
rev_file = os.path.join(db_path, revision)
|
||||||
|
if os.path.exists(rev_file):
|
||||||
|
print(f'Database files already exist for commit {revision}')
|
||||||
|
sys.exit(0)
|
||||||
|
generate_port_versions_db(ports_path=ports_path, db_path=db_path, revision=revision)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main(ports_path=os.path.join(SCRIPT_DIRECTORY, '../ports'),
|
||||||
|
db_path=os.path.join(SCRIPT_DIRECTORY, '../port_versions'))
|
@ -17,12 +17,18 @@ namespace vcpkg::Paragraphs
|
|||||||
|
|
||||||
ExpectedS<Paragraph> parse_single_paragraph(const std::string& str, const std::string& origin);
|
ExpectedS<Paragraph> parse_single_paragraph(const std::string& str, const std::string& origin);
|
||||||
ExpectedS<Paragraph> get_single_paragraph(const Files::Filesystem& fs, const fs::path& control_path);
|
ExpectedS<Paragraph> get_single_paragraph(const Files::Filesystem& fs, const fs::path& control_path);
|
||||||
|
|
||||||
ExpectedS<std::vector<Paragraph>> get_paragraphs(const Files::Filesystem& fs, const fs::path& control_path);
|
ExpectedS<std::vector<Paragraph>> get_paragraphs(const Files::Filesystem& fs, const fs::path& control_path);
|
||||||
|
ExpectedS<std::vector<Paragraph>> get_paragraphs_text(const std::string& text, const std::string& origin);
|
||||||
|
|
||||||
ExpectedS<std::vector<Paragraph>> parse_paragraphs(const std::string& str, const std::string& origin);
|
ExpectedS<std::vector<Paragraph>> parse_paragraphs(const std::string& str, const std::string& origin);
|
||||||
|
|
||||||
bool is_port_directory(const Files::Filesystem& fs, const fs::path& path);
|
bool is_port_directory(const Files::Filesystem& fs, const fs::path& path);
|
||||||
|
|
||||||
Parse::ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path);
|
Parse::ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path);
|
||||||
|
Parse::ParseExpected<SourceControlFile> try_load_port_text(const std::string& text,
|
||||||
|
const std::string& origin,
|
||||||
|
bool is_manifest);
|
||||||
|
|
||||||
ExpectedS<BinaryControlFile> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec);
|
ExpectedS<BinaryControlFile> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec);
|
||||||
|
|
||||||
|
@ -91,11 +91,14 @@ namespace vcpkg
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Parse::ParseExpected<SourceControlFile> parse_manifest_object(const std::string& origin,
|
||||||
|
const Json::Object& object);
|
||||||
|
|
||||||
static Parse::ParseExpected<SourceControlFile> parse_manifest_file(const fs::path& path_to_manifest,
|
static Parse::ParseExpected<SourceControlFile> parse_manifest_file(const fs::path& path_to_manifest,
|
||||||
const Json::Object& object);
|
const Json::Object& object);
|
||||||
|
|
||||||
static Parse::ParseExpected<SourceControlFile> parse_control_file(
|
static Parse::ParseExpected<SourceControlFile> parse_control_file(
|
||||||
const fs::path& path_to_control, std::vector<Parse::Paragraph>&& control_paragraphs);
|
const std::string& origin, std::vector<Parse::Paragraph>&& control_paragraphs);
|
||||||
|
|
||||||
// Always non-null in non-error cases
|
// Always non-null in non-error cases
|
||||||
std::unique_ptr<SourceParagraph> core_paragraph;
|
std::unique_ptr<SourceParagraph> core_paragraph;
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vcpkg/fwd/vcpkgpaths.h>
|
|
||||||
|
|
||||||
namespace vcpkg::Versions
|
namespace vcpkg::Versions
|
||||||
{
|
{
|
||||||
enum class Scheme
|
enum class Scheme
|
||||||
{
|
{
|
||||||
String,
|
|
||||||
Relaxed,
|
Relaxed,
|
||||||
Semver,
|
Semver,
|
||||||
Date
|
Date,
|
||||||
|
String
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ Build-Depends: bzip
|
|||||||
)",
|
)",
|
||||||
"<testdata>");
|
"<testdata>");
|
||||||
REQUIRE(pghs.has_value());
|
REQUIRE(pghs.has_value());
|
||||||
auto maybe_scf = SourceControlFile::parse_control_file(fs::path(), std::move(*pghs.get()));
|
auto maybe_scf = SourceControlFile::parse_control_file(fs::u8string(fs::path()), std::move(*pghs.get()));
|
||||||
REQUIRE(maybe_scf.has_value());
|
REQUIRE(maybe_scf.has_value());
|
||||||
SourceControlFileLocation scfl{std::move(*maybe_scf.get()), fs::path()};
|
SourceControlFileLocation scfl{std::move(*maybe_scf.get()), fs::path()};
|
||||||
|
|
||||||
@ -255,7 +255,7 @@ Description: a spiffy compression library wrapper
|
|||||||
)",
|
)",
|
||||||
"<testdata>");
|
"<testdata>");
|
||||||
REQUIRE(pghs.has_value());
|
REQUIRE(pghs.has_value());
|
||||||
auto maybe_scf = SourceControlFile::parse_control_file(fs::path(), std::move(*pghs.get()));
|
auto maybe_scf = SourceControlFile::parse_control_file(fs::u8string(fs::path()), std::move(*pghs.get()));
|
||||||
REQUIRE(maybe_scf.has_value());
|
REQUIRE(maybe_scf.has_value());
|
||||||
SourceControlFileLocation scfl{std::move(*maybe_scf.get()), fs::path()};
|
SourceControlFileLocation scfl{std::move(*maybe_scf.get()), fs::path()};
|
||||||
plan.install_actions.push_back(Dependencies::InstallPlanAction());
|
plan.install_actions.push_back(Dependencies::InstallPlanAction());
|
||||||
@ -278,7 +278,7 @@ Description: a spiffy compression library wrapper
|
|||||||
)",
|
)",
|
||||||
"<testdata>");
|
"<testdata>");
|
||||||
REQUIRE(pghs2.has_value());
|
REQUIRE(pghs2.has_value());
|
||||||
auto maybe_scf2 = SourceControlFile::parse_control_file(fs::path(), std::move(*pghs2.get()));
|
auto maybe_scf2 = SourceControlFile::parse_control_file(fs::u8string(fs::path()), std::move(*pghs2.get()));
|
||||||
REQUIRE(maybe_scf2.has_value());
|
REQUIRE(maybe_scf2.has_value());
|
||||||
SourceControlFileLocation scfl2{std::move(*maybe_scf2.get()), fs::path()};
|
SourceControlFileLocation scfl2{std::move(*maybe_scf2.get()), fs::path()};
|
||||||
plan.install_actions.push_back(Dependencies::InstallPlanAction());
|
plan.install_actions.push_back(Dependencies::InstallPlanAction());
|
||||||
|
@ -331,9 +331,9 @@ TEST_CASE ("Serialize all the ports", "[manifests]")
|
|||||||
auto pghs = Paragraphs::parse_paragraphs(contents, fs::u8string(control));
|
auto pghs = Paragraphs::parse_paragraphs(contents, fs::u8string(control));
|
||||||
REQUIRE(pghs);
|
REQUIRE(pghs);
|
||||||
|
|
||||||
scfs.push_back(std::move(
|
scfs.push_back(std::move(*SourceControlFile::parse_control_file(
|
||||||
*SourceControlFile::parse_control_file(control, std::move(pghs).value_or_exit(VCPKG_LINE_INFO))
|
fs::u8string(control), std::move(pghs).value_or_exit(VCPKG_LINE_INFO))
|
||||||
.value_or_exit(VCPKG_LINE_INFO)));
|
.value_or_exit(VCPKG_LINE_INFO)));
|
||||||
}
|
}
|
||||||
else if (fs.exists(manifest))
|
else if (fs.exists(manifest))
|
||||||
{
|
{
|
||||||
|
@ -51,6 +51,38 @@ TEST_CASE ("SourceParagraph construct minimum", "[paragraph]")
|
|||||||
REQUIRE(pgh.core_paragraph->dependencies.size() == 0);
|
REQUIRE(pgh.core_paragraph->dependencies.size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE ("SourceParagraph construct invalid", "[paragraph]")
|
||||||
|
{
|
||||||
|
auto m_pgh = test_parse_control_file({{
|
||||||
|
{"Source", "zlib"},
|
||||||
|
{"Version", "1.2.8"},
|
||||||
|
{"Build-Depends", "1.2.8"},
|
||||||
|
}});
|
||||||
|
|
||||||
|
REQUIRE(!m_pgh.has_value());
|
||||||
|
|
||||||
|
m_pgh = test_parse_control_file({{
|
||||||
|
{"Source", "zlib"},
|
||||||
|
{"Version", "1.2.8"},
|
||||||
|
{"Default-Features", "1.2.8"},
|
||||||
|
}});
|
||||||
|
|
||||||
|
REQUIRE(!m_pgh.has_value());
|
||||||
|
|
||||||
|
m_pgh = test_parse_control_file({
|
||||||
|
{
|
||||||
|
{"Source", "zlib"},
|
||||||
|
{"Version", "1.2.8"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{"Feature", "a"},
|
||||||
|
{"Build-Depends", "1.2.8"},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
REQUIRE(!m_pgh.has_value());
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE ("SourceParagraph construct maximum", "[paragraph]")
|
TEST_CASE ("SourceParagraph construct maximum", "[paragraph]")
|
||||||
{
|
{
|
||||||
auto m_pgh = test_parse_control_file({{
|
auto m_pgh = test_parse_control_file({{
|
||||||
@ -76,6 +108,24 @@ TEST_CASE ("SourceParagraph construct maximum", "[paragraph]")
|
|||||||
REQUIRE(pgh.core_paragraph->default_features[0] == "df");
|
REQUIRE(pgh.core_paragraph->default_features[0] == "df");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE ("SourceParagraph construct feature", "[paragraph]")
|
||||||
|
{
|
||||||
|
auto m_pgh = test_parse_control_file({
|
||||||
|
{
|
||||||
|
{"Source", "s"},
|
||||||
|
{"Version", "v"},
|
||||||
|
},
|
||||||
|
{{"Feature", "f"}, {"Description", "d2"}, {"Build-Depends", "bd2"}},
|
||||||
|
});
|
||||||
|
REQUIRE(m_pgh.has_value());
|
||||||
|
auto& pgh = **m_pgh.get();
|
||||||
|
|
||||||
|
REQUIRE(pgh.feature_paragraphs.size() == 1);
|
||||||
|
REQUIRE(pgh.feature_paragraphs[0]->name == "f");
|
||||||
|
REQUIRE(pgh.feature_paragraphs[0]->description == std::vector<std::string>{"d2"});
|
||||||
|
REQUIRE(pgh.feature_paragraphs[0]->dependencies.size() == 1);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE ("SourceParagraph two dependencies", "[paragraph]")
|
TEST_CASE ("SourceParagraph two dependencies", "[paragraph]")
|
||||||
{
|
{
|
||||||
auto m_pgh = test_parse_control_file({{
|
auto m_pgh = test_parse_control_file({{
|
||||||
|
@ -80,8 +80,8 @@ namespace
|
|||||||
paragraphs.error());
|
paragraphs.error());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
auto scf_res =
|
auto scf_res = SourceControlFile::parse_control_file(fs::u8string(control_path),
|
||||||
SourceControlFile::parse_control_file(control_path, std::move(paragraphs).value_or_exit(VCPKG_LINE_INFO));
|
std::move(paragraphs).value_or_exit(VCPKG_LINE_INFO));
|
||||||
if (!scf_res)
|
if (!scf_res)
|
||||||
{
|
{
|
||||||
System::printf(System::Color::error, "Failed to parse control file: %s\n", control_path_string);
|
System::printf(System::Color::error, "Failed to parse control file: %s\n", control_path_string);
|
||||||
|
@ -1,77 +1,257 @@
|
|||||||
|
#include <vcpkg/base/json.h>
|
||||||
#include <vcpkg/base/system.print.h>
|
#include <vcpkg/base/system.print.h>
|
||||||
#include <vcpkg/base/system.process.h>
|
#include <vcpkg/base/system.process.h>
|
||||||
#include <vcpkg/base/util.h>
|
#include <vcpkg/base/util.h>
|
||||||
|
|
||||||
#include <vcpkg/commands.porthistory.h>
|
#include <vcpkg/commands.porthistory.h>
|
||||||
#include <vcpkg/help.h>
|
#include <vcpkg/help.h>
|
||||||
|
#include <vcpkg/paragraphs.h>
|
||||||
#include <vcpkg/tools.h>
|
#include <vcpkg/tools.h>
|
||||||
#include <vcpkg/vcpkgcmdarguments.h>
|
#include <vcpkg/vcpkgcmdarguments.h>
|
||||||
#include <vcpkg/vcpkgpaths.h>
|
#include <vcpkg/vcpkgpaths.h>
|
||||||
|
#include <vcpkg/versions.h>
|
||||||
|
|
||||||
namespace vcpkg::Commands::PortHistory
|
namespace vcpkg::Commands::PortHistory
|
||||||
{
|
{
|
||||||
struct PortControlVersion
|
namespace
|
||||||
{
|
{
|
||||||
std::string commit_id;
|
struct HistoryVersion
|
||||||
std::string version;
|
|
||||||
std::string date;
|
|
||||||
};
|
|
||||||
|
|
||||||
static System::ExitCodeAndOutput run_git_command(const VcpkgPaths& paths, const std::string& cmd)
|
|
||||||
{
|
|
||||||
const fs::path& git_exe = paths.get_tool_exe(Tools::GIT);
|
|
||||||
const fs::path dot_git_dir = paths.root / ".git";
|
|
||||||
|
|
||||||
const std::string full_cmd =
|
|
||||||
Strings::format(R"("%s" --git-dir="%s" %s)", fs::u8string(git_exe), fs::u8string(dot_git_dir), cmd);
|
|
||||||
|
|
||||||
auto output = System::cmd_execute_and_capture_output(full_cmd);
|
|
||||||
Checks::check_exit(VCPKG_LINE_INFO, output.exit_code == 0, "Failed to run command: %s", full_cmd);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::string get_version_from_commit(const VcpkgPaths& paths,
|
|
||||||
const std::string& commit_id,
|
|
||||||
const std::string& port_name)
|
|
||||||
{
|
|
||||||
const std::string cmd = Strings::format(R"(show %s:ports/%s/CONTROL)", commit_id, port_name);
|
|
||||||
auto output = run_git_command(paths, cmd);
|
|
||||||
|
|
||||||
const auto version = Strings::find_at_most_one_enclosed(output.output, "\nVersion: ", "\n");
|
|
||||||
const auto port_version = Strings::find_at_most_one_enclosed(output.output, "\nPort-Version: ", "\n");
|
|
||||||
Checks::check_exit(VCPKG_LINE_INFO, version.has_value(), "CONTROL file does not have a 'Version' field");
|
|
||||||
if (auto pv = port_version.get())
|
|
||||||
{
|
{
|
||||||
return Strings::format("%s#%s", version.get()->to_string(), pv->to_string());
|
std::string port_name;
|
||||||
|
std::string git_tree;
|
||||||
|
std::string commit_id;
|
||||||
|
std::string commit_date;
|
||||||
|
std::string version_string;
|
||||||
|
std::string version;
|
||||||
|
int port_version;
|
||||||
|
Versions::Scheme scheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
const System::ExitCodeAndOutput run_git_command_inner(const VcpkgPaths& paths,
|
||||||
|
const fs::path& dot_git_directory,
|
||||||
|
const fs::path& working_directory,
|
||||||
|
const std::string& cmd)
|
||||||
|
{
|
||||||
|
const fs::path& git_exe = paths.get_tool_exe(Tools::GIT);
|
||||||
|
|
||||||
|
System::CmdLineBuilder builder;
|
||||||
|
builder.path_arg(git_exe)
|
||||||
|
.string_arg(Strings::concat("--git-dir=", fs::u8string(dot_git_directory)))
|
||||||
|
.string_arg(Strings::concat("--work-tree=", fs::u8string(working_directory)));
|
||||||
|
const std::string full_cmd = Strings::concat(builder.extract(), " ", cmd);
|
||||||
|
|
||||||
|
const auto output = System::cmd_execute_and_capture_output(full_cmd);
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
return version.get()->to_string();
|
const System::ExitCodeAndOutput run_git_command(const VcpkgPaths& paths, const std::string& cmd)
|
||||||
}
|
|
||||||
|
|
||||||
static std::vector<PortControlVersion> read_versions_from_log(const VcpkgPaths& paths, const std::string& port_name)
|
|
||||||
{
|
|
||||||
const std::string cmd =
|
|
||||||
Strings::format(R"(log --format="%%H %%cd" --date=short --left-only -- ports/%s/.)", port_name);
|
|
||||||
auto output = run_git_command(paths, cmd);
|
|
||||||
|
|
||||||
auto commits = Util::fmap(
|
|
||||||
Strings::split(output.output, '\n'), [](const std::string& line) -> auto {
|
|
||||||
auto parts = Strings::split(line, ' ');
|
|
||||||
return std::make_pair(parts[0], parts[1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
std::vector<PortControlVersion> ret;
|
|
||||||
std::string last_version;
|
|
||||||
for (auto&& commit_date_pair : commits)
|
|
||||||
{
|
{
|
||||||
const std::string version = get_version_from_commit(paths, commit_date_pair.first, port_name);
|
const fs::path& work_dir = paths.root;
|
||||||
if (last_version != version)
|
const fs::path dot_git_dir = paths.root / ".git";
|
||||||
|
|
||||||
|
return run_git_command_inner(paths, dot_git_dir, work_dir, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_date(const std::string& version_string)
|
||||||
|
{
|
||||||
|
// The date regex is not complete, it matches strings that look like dates,
|
||||||
|
// e.g.: 2020-99-99.
|
||||||
|
//
|
||||||
|
// The regex has two capture groups:
|
||||||
|
// * Date: "^([0-9]{4,}[-][0-9]{2}[-][0-9]{2})", it matches strings that resemble YYYY-MM-DD.
|
||||||
|
// It does not validate that MM <= 12, or that DD is possible with the given MM.
|
||||||
|
// YYYY should be AT LEAST 4 digits, for some kind of "future proofing".
|
||||||
|
std::regex re("^([0-9]{4,}[-][0-9]{2}[-][0-9]{2})((?:[.|-][0-9a-zA-Z]+)*)$");
|
||||||
|
return std::regex_match(version_string, re);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_date_without_tags(const std::string& version_string)
|
||||||
|
{
|
||||||
|
std::regex re("^([0-9]{4,}[-][0-9]{2}[-][0-9]{2})$");
|
||||||
|
return std::regex_match(version_string, re);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_semver(const std::string& version_string)
|
||||||
|
{
|
||||||
|
// This is the "official" SemVer regex, taken from:
|
||||||
|
// https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
|
||||||
|
std::regex re("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*"
|
||||||
|
")(?:\\.(?:0|["
|
||||||
|
"1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
|
||||||
|
return std::regex_match(version_string, re);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_semver_relaxed(const std::string& version_string)
|
||||||
|
{
|
||||||
|
std::regex re("^(?:[0-9a-zA-Z]+)\\.(?:[0-9a-zA-Z]+)\\.(?:[0-9a-zA-Z]+)(?:[\\.|-|\\+][0-9a-zA-Z]+)*$");
|
||||||
|
return std::regex_match(version_string, re);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Versions::Scheme guess_version_scheme(const std::string& version_string)
|
||||||
|
{
|
||||||
|
if (is_date(version_string))
|
||||||
{
|
{
|
||||||
ret.emplace_back(PortControlVersion{commit_date_pair.first, version, commit_date_pair.second});
|
return Versions::Scheme::Date;
|
||||||
last_version = version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (is_semver(version_string) || is_semver_relaxed(version_string))
|
||||||
|
{
|
||||||
|
return Versions::Scheme::Relaxed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Versions::Scheme::String;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<std::string, int> clean_version_string(const std::string& version_string,
|
||||||
|
int port_version,
|
||||||
|
bool from_manifest)
|
||||||
|
{
|
||||||
|
// Manifest files and ports that use the `Port-Version` field are assumed to have a clean version string
|
||||||
|
// already.
|
||||||
|
if (from_manifest || port_version > 0)
|
||||||
|
{
|
||||||
|
return std::make_pair(version_string, port_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string clean_version = version_string;
|
||||||
|
int clean_port_version = 0;
|
||||||
|
|
||||||
|
const auto index = version_string.find_last_of('-');
|
||||||
|
if (index != std::string::npos)
|
||||||
|
{
|
||||||
|
// Very lazy check to keep date versions untouched
|
||||||
|
if (!is_date_without_tags(version_string))
|
||||||
|
{
|
||||||
|
auto maybe_port_version = version_string.substr(index + 1);
|
||||||
|
clean_version.resize(index);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clean_port_version = std::stoi(maybe_port_version);
|
||||||
|
}
|
||||||
|
catch (std::exception&)
|
||||||
|
{
|
||||||
|
// If not convertible to int consider last fragment as part of version string
|
||||||
|
clean_version = version_string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_pair(clean_version, clean_port_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
vcpkg::Optional<HistoryVersion> get_version_from_text(const std::string& text,
|
||||||
|
const std::string& git_tree,
|
||||||
|
const std::string& commit_id,
|
||||||
|
const std::string& commit_date,
|
||||||
|
const std::string& port_name,
|
||||||
|
bool is_manifest)
|
||||||
|
{
|
||||||
|
auto res = Paragraphs::try_load_port_text(text, Strings::concat(commit_id, ":", port_name), is_manifest);
|
||||||
|
if (const auto& maybe_scf = res.get())
|
||||||
|
{
|
||||||
|
if (const auto& scf = maybe_scf->get())
|
||||||
|
{
|
||||||
|
// TODO: Get clean version name and port version
|
||||||
|
const auto version_string = scf->core_paragraph->version;
|
||||||
|
const auto clean_version =
|
||||||
|
clean_version_string(version_string, scf->core_paragraph->port_version, is_manifest);
|
||||||
|
|
||||||
|
// SCF to HistoryVersion
|
||||||
|
return HistoryVersion{
|
||||||
|
port_name,
|
||||||
|
git_tree,
|
||||||
|
commit_id,
|
||||||
|
commit_date,
|
||||||
|
Strings::concat(clean_version.first, "#", std::to_string(clean_version.second)),
|
||||||
|
clean_version.first,
|
||||||
|
clean_version.second,
|
||||||
|
guess_version_scheme(clean_version.first)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
vcpkg::Optional<HistoryVersion> get_version_from_commit(const VcpkgPaths& paths,
|
||||||
|
const std::string& commit_id,
|
||||||
|
const std::string& commit_date,
|
||||||
|
const std::string& port_name)
|
||||||
|
{
|
||||||
|
const std::string rev_parse_cmd = Strings::format("rev-parse %s:ports/%s", commit_id, port_name);
|
||||||
|
auto rev_parse_output = run_git_command(paths, rev_parse_cmd);
|
||||||
|
if (rev_parse_output.exit_code == 0)
|
||||||
|
{
|
||||||
|
// Remove newline character
|
||||||
|
const auto git_tree = Strings::trim(std::move(rev_parse_output.output));
|
||||||
|
|
||||||
|
// Do we have a manifest file?
|
||||||
|
const std::string manifest_cmd = Strings::format(R"(show %s:vcpkg.json)", git_tree, port_name);
|
||||||
|
auto manifest_output = run_git_command(paths, manifest_cmd);
|
||||||
|
if (manifest_output.exit_code == 0)
|
||||||
|
{
|
||||||
|
return get_version_from_text(
|
||||||
|
manifest_output.output, git_tree, commit_id, commit_date, port_name, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string cmd = Strings::format(R"(show %s:CONTROL)", git_tree, commit_id, port_name);
|
||||||
|
auto control_output = run_git_command(paths, cmd);
|
||||||
|
|
||||||
|
if (control_output.exit_code == 0)
|
||||||
|
{
|
||||||
|
return get_version_from_text(
|
||||||
|
control_output.output, git_tree, commit_id, commit_date, port_name, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<HistoryVersion> read_versions_from_log(const VcpkgPaths& paths, const std::string& port_name)
|
||||||
|
{
|
||||||
|
// log --format="%H %cd" --date=short --left-only -- ports/{port_name}/.
|
||||||
|
System::CmdLineBuilder builder;
|
||||||
|
builder.string_arg("log");
|
||||||
|
builder.string_arg("--format=%H %cd");
|
||||||
|
builder.string_arg("--date=short");
|
||||||
|
builder.string_arg("--left-only");
|
||||||
|
builder.string_arg("--"); // Begin pathspec
|
||||||
|
builder.string_arg(Strings::format("ports/%s/.", port_name));
|
||||||
|
const auto output = run_git_command(paths, builder.extract());
|
||||||
|
|
||||||
|
auto commits = Util::fmap(
|
||||||
|
Strings::split(output.output, '\n'), [](const std::string& line) -> auto {
|
||||||
|
auto parts = Strings::split(line, ' ');
|
||||||
|
return std::make_pair(parts[0], parts[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<HistoryVersion> ret;
|
||||||
|
std::string last_version;
|
||||||
|
for (auto&& commit_date_pair : commits)
|
||||||
|
{
|
||||||
|
auto maybe_version =
|
||||||
|
get_version_from_commit(paths, commit_date_pair.first, commit_date_pair.second, port_name);
|
||||||
|
if (maybe_version.has_value())
|
||||||
|
{
|
||||||
|
const auto version = maybe_version.value_or_exit(VCPKG_LINE_INFO);
|
||||||
|
|
||||||
|
// Keep latest port with the current version string
|
||||||
|
if (last_version != version.version_string)
|
||||||
|
{
|
||||||
|
last_version = version.version_string;
|
||||||
|
ret.emplace_back(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NOTE: Uncomment this code if you're looking for edge cases to patch in the generation.
|
||||||
|
// Otherwise, x-history simply skips "bad" versions, which is OK behavior.
|
||||||
|
// else
|
||||||
|
//{
|
||||||
|
// Checks::exit_with_message(VCPKG_LINE_INFO, "Failed to get version from %s:%s",
|
||||||
|
// commit_date_pair.first, port_name);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CommandStructure COMMAND_STRUCTURE = {
|
const CommandStructure COMMAND_STRUCTURE = {
|
||||||
@ -84,13 +264,47 @@ namespace vcpkg::Commands::PortHistory
|
|||||||
|
|
||||||
void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths)
|
void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths)
|
||||||
{
|
{
|
||||||
(void)args.parse_arguments(COMMAND_STRUCTURE);
|
const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE);
|
||||||
|
|
||||||
std::string port_name = args.command_arguments.at(0);
|
std::string port_name = args.command_arguments.at(0);
|
||||||
std::vector<PortControlVersion> versions = read_versions_from_log(paths, port_name);
|
std::vector<HistoryVersion> versions = read_versions_from_log(paths, port_name);
|
||||||
System::print2(" version date vcpkg commit\n");
|
|
||||||
for (auto&& version : versions)
|
if (args.output_json())
|
||||||
{
|
{
|
||||||
System::printf("%20.20s %s %s\n", version.version, version.date, version.commit_id);
|
Json::Array versions_json;
|
||||||
|
for (auto&& version : versions)
|
||||||
|
{
|
||||||
|
Json::Object object;
|
||||||
|
object.insert("git-tree", Json::Value::string(version.git_tree));
|
||||||
|
switch (version.scheme)
|
||||||
|
{
|
||||||
|
case Versions::Scheme::Semver: // falls through
|
||||||
|
case Versions::Scheme::Relaxed:
|
||||||
|
object.insert("version", Json::Value::string(version.version));
|
||||||
|
break;
|
||||||
|
case Versions::Scheme::Date:
|
||||||
|
object.insert("version-date", Json::Value::string(version.version));
|
||||||
|
break;
|
||||||
|
case Versions::Scheme::String: // falls through
|
||||||
|
default: object.insert("version-string", Json::Value::string(version.version)); break;
|
||||||
|
}
|
||||||
|
object.insert("port-version", Json::Value::integer(version.port_version));
|
||||||
|
versions_json.push_back(std::move(object));
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Object root;
|
||||||
|
root.insert("versions", versions_json);
|
||||||
|
|
||||||
|
auto json_string = Json::stringify(root, vcpkg::Json::JsonStyle::with_spaces(2));
|
||||||
|
System::printf("%s\n", json_string);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System::print2(" version date vcpkg commit\n");
|
||||||
|
for (auto&& version : versions)
|
||||||
|
{
|
||||||
|
System::printf("%20.20s %s %s\n", version.version_string, version.commit_date, version.commit_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Checks::exit_success(VCPKG_LINE_INFO);
|
Checks::exit_success(VCPKG_LINE_INFO);
|
||||||
}
|
}
|
||||||
|
@ -241,6 +241,11 @@ namespace vcpkg::Paragraphs
|
|||||||
return contents.error().message();
|
return contents.error().message();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpectedS<std::vector<Paragraph>> get_paragraphs_text(const std::string& text, const std::string& origin)
|
||||||
|
{
|
||||||
|
return parse_paragraphs(text, origin);
|
||||||
|
}
|
||||||
|
|
||||||
ExpectedS<std::vector<Paragraph>> get_paragraphs(const Files::Filesystem& fs, const fs::path& control_path)
|
ExpectedS<std::vector<Paragraph>> get_paragraphs(const Files::Filesystem& fs, const fs::path& control_path)
|
||||||
{
|
{
|
||||||
const Expected<std::string> contents = fs.read_contents(control_path);
|
const Expected<std::string> contents = fs.read_contents(control_path);
|
||||||
@ -262,36 +267,73 @@ namespace vcpkg::Paragraphs
|
|||||||
return fs.exists(path / fs::u8path("CONTROL")) || fs.exists(path / fs::u8path("vcpkg.json"));
|
return fs.exists(path / fs::u8path("CONTROL")) || fs.exists(path / fs::u8path("vcpkg.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ParseExpected<SourceControlFile> try_load_manifest(const Files::Filesystem& fs,
|
static ParseExpected<SourceControlFile> try_load_manifest_object(
|
||||||
const std::string& port_name,
|
const std::string& origin,
|
||||||
const fs::path& path_to_manifest,
|
const ExpectedT<std::pair<vcpkg::Json::Value, vcpkg::Json::JsonStyle>, std::unique_ptr<Parse::IParseError>>&
|
||||||
std::error_code& ec)
|
res)
|
||||||
{
|
{
|
||||||
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
auto res = Json::parse_file(fs, path_to_manifest, ec);
|
|
||||||
if (ec) return error_info;
|
|
||||||
|
|
||||||
if (auto val = res.get())
|
if (auto val = res.get())
|
||||||
{
|
{
|
||||||
if (val->first.is_object())
|
if (val->first.is_object())
|
||||||
{
|
{
|
||||||
return SourceControlFile::parse_manifest_file(path_to_manifest, val->first.object());
|
return SourceControlFile::parse_manifest_object(origin, val->first.object());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
error_info->name = port_name;
|
error_info->name = origin;
|
||||||
error_info->error = "Manifest files must have a top-level object";
|
error_info->error = "Manifest files must have a top-level object";
|
||||||
return error_info;
|
return error_info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
error_info->name = port_name;
|
error_info->name = origin;
|
||||||
error_info->error = res.error()->format();
|
error_info->error = res.error()->format();
|
||||||
return error_info;
|
return error_info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ParseExpected<SourceControlFile> try_load_manifest_text(const std::string& text, const std::string& origin)
|
||||||
|
{
|
||||||
|
auto res = Json::parse(text);
|
||||||
|
return try_load_manifest_object(origin, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ParseExpected<SourceControlFile> try_load_manifest(const Files::Filesystem& fs,
|
||||||
|
const std::string& port_name,
|
||||||
|
const fs::path& path_to_manifest,
|
||||||
|
std::error_code& ec)
|
||||||
|
{
|
||||||
|
(void)port_name;
|
||||||
|
|
||||||
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
|
auto res = Json::parse_file(fs, path_to_manifest, ec);
|
||||||
|
if (ec) return error_info;
|
||||||
|
|
||||||
|
return try_load_manifest_object(fs::u8string(path_to_manifest), res);
|
||||||
|
}
|
||||||
|
|
||||||
|
ParseExpected<SourceControlFile> try_load_port_text(const std::string& text,
|
||||||
|
const std::string& origin,
|
||||||
|
bool is_manifest)
|
||||||
|
{
|
||||||
|
if (is_manifest)
|
||||||
|
{
|
||||||
|
return try_load_manifest_text(text, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpectedS<std::vector<Paragraph>> pghs = get_paragraphs_text(text, origin);
|
||||||
|
if (auto vector_pghs = pghs.get())
|
||||||
|
{
|
||||||
|
return SourceControlFile::parse_control_file(origin, std::move(*vector_pghs));
|
||||||
|
}
|
||||||
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
|
error_info->name = fs::u8string(origin);
|
||||||
|
error_info->error = pghs.error();
|
||||||
|
return error_info;
|
||||||
|
}
|
||||||
|
|
||||||
ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path)
|
ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path)
|
||||||
{
|
{
|
||||||
const auto path_to_manifest = path / fs::u8path("vcpkg.json");
|
const auto path_to_manifest = path / fs::u8path("vcpkg.json");
|
||||||
@ -318,7 +360,7 @@ namespace vcpkg::Paragraphs
|
|||||||
ExpectedS<std::vector<Paragraph>> pghs = get_paragraphs(fs, path_to_control);
|
ExpectedS<std::vector<Paragraph>> pghs = get_paragraphs(fs, path_to_control);
|
||||||
if (auto vector_pghs = pghs.get())
|
if (auto vector_pghs = pghs.get())
|
||||||
{
|
{
|
||||||
return SourceControlFile::parse_control_file(path_to_control, std::move(*vector_pghs));
|
return SourceControlFile::parse_control_file(fs::u8string(path_to_control), std::move(*vector_pghs));
|
||||||
}
|
}
|
||||||
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
error_info->name = fs::u8string(path.filename());
|
error_info->name = fs::u8string(path.filename());
|
||||||
|
@ -221,7 +221,7 @@ namespace vcpkg
|
|||||||
|
|
||||||
fpgh.extra_info.sort_keys();
|
fpgh.extra_info.sort_keys();
|
||||||
}
|
}
|
||||||
void operator()(SourceControlFile& scf) const
|
[[nodiscard]] std::unique_ptr<ParseControlErrorInfo> operator()(SourceControlFile& scf) const
|
||||||
{
|
{
|
||||||
(*this)(*scf.core_paragraph);
|
(*this)(*scf.core_paragraph);
|
||||||
std::for_each(scf.feature_paragraphs.begin(), scf.feature_paragraphs.end(), *this);
|
std::for_each(scf.feature_paragraphs.begin(), scf.feature_paragraphs.end(), *this);
|
||||||
@ -231,20 +231,21 @@ namespace vcpkg
|
|||||||
std::adjacent_find(scf.feature_paragraphs.begin(), scf.feature_paragraphs.end(), FeatureEqual{});
|
std::adjacent_find(scf.feature_paragraphs.begin(), scf.feature_paragraphs.end(), FeatureEqual{});
|
||||||
if (adjacent_equal != scf.feature_paragraphs.end())
|
if (adjacent_equal != scf.feature_paragraphs.end())
|
||||||
{
|
{
|
||||||
Checks::exit_with_message(VCPKG_LINE_INFO,
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
R"(Multiple features with the same name for port %s: %s
|
error_info->name = scf.core_paragraph->name;
|
||||||
|
error_info->error = Strings::format(R"(Multiple features with the same name for port %s: %s
|
||||||
This is invalid; please make certain that features have distinct names.)",
|
This is invalid; please make certain that features have distinct names.)",
|
||||||
scf.core_paragraph->name,
|
scf.core_paragraph->name,
|
||||||
(*adjacent_equal)->name);
|
(*adjacent_equal)->name);
|
||||||
|
return error_info;
|
||||||
}
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
} canonicalize{};
|
} canonicalize{};
|
||||||
}
|
}
|
||||||
|
|
||||||
static ParseExpected<SourceParagraph> parse_source_paragraph(const fs::path& path_to_control, Paragraph&& fields)
|
static ParseExpected<SourceParagraph> parse_source_paragraph(const std::string& origin, Paragraph&& fields)
|
||||||
{
|
{
|
||||||
auto origin = fs::u8string(path_to_control);
|
|
||||||
|
|
||||||
ParagraphParser parser(std::move(fields));
|
ParagraphParser parser(std::move(fields));
|
||||||
|
|
||||||
auto spgh = std::make_unique<SourceParagraph>();
|
auto spgh = std::make_unique<SourceParagraph>();
|
||||||
@ -276,10 +277,35 @@ namespace vcpkg
|
|||||||
TextRowCol textrowcol;
|
TextRowCol textrowcol;
|
||||||
std::string buf;
|
std::string buf;
|
||||||
parser.optional_field(SourceParagraphFields::BUILD_DEPENDS, {buf, textrowcol});
|
parser.optional_field(SourceParagraphFields::BUILD_DEPENDS, {buf, textrowcol});
|
||||||
spgh->dependencies = parse_dependencies_list(buf, origin, textrowcol).value_or_exit(VCPKG_LINE_INFO);
|
|
||||||
|
auto maybe_dependencies = parse_dependencies_list(buf, origin, textrowcol);
|
||||||
|
if (maybe_dependencies.has_value())
|
||||||
|
{
|
||||||
|
spgh->dependencies = maybe_dependencies.value_or_exit(VCPKG_LINE_INFO);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
|
error_info->name = origin;
|
||||||
|
error_info->error = maybe_dependencies.error();
|
||||||
|
return error_info;
|
||||||
|
}
|
||||||
|
|
||||||
buf.clear();
|
buf.clear();
|
||||||
parser.optional_field(SourceParagraphFields::DEFAULT_FEATURES, {buf, textrowcol});
|
parser.optional_field(SourceParagraphFields::DEFAULT_FEATURES, {buf, textrowcol});
|
||||||
spgh->default_features = parse_default_features_list(buf, origin, textrowcol).value_or_exit(VCPKG_LINE_INFO);
|
|
||||||
|
auto maybe_default_features = parse_default_features_list(buf, origin, textrowcol);
|
||||||
|
if (maybe_default_features.has_value())
|
||||||
|
{
|
||||||
|
spgh->default_features = maybe_default_features.value_or_exit(VCPKG_LINE_INFO);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
|
error_info->name = origin;
|
||||||
|
error_info->error = maybe_default_features.error();
|
||||||
|
return error_info;
|
||||||
|
}
|
||||||
|
|
||||||
auto supports_expr = parser.optional_field(SourceParagraphFields::SUPPORTS);
|
auto supports_expr = parser.optional_field(SourceParagraphFields::SUPPORTS);
|
||||||
if (!supports_expr.empty())
|
if (!supports_expr.empty())
|
||||||
@ -304,9 +330,8 @@ namespace vcpkg
|
|||||||
return spgh;
|
return spgh;
|
||||||
}
|
}
|
||||||
|
|
||||||
static ParseExpected<FeatureParagraph> parse_feature_paragraph(const fs::path& path_to_control, Paragraph&& fields)
|
static ParseExpected<FeatureParagraph> parse_feature_paragraph(const std::string& origin, Paragraph&& fields)
|
||||||
{
|
{
|
||||||
auto origin = fs::u8string(path_to_control);
|
|
||||||
ParagraphParser parser(std::move(fields));
|
ParagraphParser parser(std::move(fields));
|
||||||
|
|
||||||
auto fpgh = std::make_unique<FeatureParagraph>();
|
auto fpgh = std::make_unique<FeatureParagraph>();
|
||||||
@ -315,9 +340,19 @@ namespace vcpkg
|
|||||||
fpgh->description = Strings::split(parser.required_field(SourceParagraphFields::DESCRIPTION), '\n');
|
fpgh->description = Strings::split(parser.required_field(SourceParagraphFields::DESCRIPTION), '\n');
|
||||||
trim_all(fpgh->description);
|
trim_all(fpgh->description);
|
||||||
|
|
||||||
fpgh->dependencies =
|
auto maybe_dependencies =
|
||||||
parse_dependencies_list(parser.optional_field(SourceParagraphFields::BUILD_DEPENDS), origin)
|
parse_dependencies_list(parser.optional_field(SourceParagraphFields::BUILD_DEPENDS), origin);
|
||||||
.value_or_exit(VCPKG_LINE_INFO);
|
if (maybe_dependencies.has_value())
|
||||||
|
{
|
||||||
|
fpgh->dependencies = maybe_dependencies.value_or_exit(VCPKG_LINE_INFO);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto error_info = std::make_unique<ParseControlErrorInfo>();
|
||||||
|
error_info->name = origin;
|
||||||
|
error_info->error = maybe_dependencies.error();
|
||||||
|
return error_info;
|
||||||
|
}
|
||||||
|
|
||||||
auto err = parser.error_info(fpgh->name.empty() ? origin : fpgh->name);
|
auto err = parser.error_info(fpgh->name.empty() ? origin : fpgh->name);
|
||||||
if (err)
|
if (err)
|
||||||
@ -327,18 +362,18 @@ namespace vcpkg
|
|||||||
}
|
}
|
||||||
|
|
||||||
ParseExpected<SourceControlFile> SourceControlFile::parse_control_file(
|
ParseExpected<SourceControlFile> SourceControlFile::parse_control_file(
|
||||||
const fs::path& path_to_control, std::vector<Parse::Paragraph>&& control_paragraphs)
|
const std::string& origin, std::vector<Parse::Paragraph>&& control_paragraphs)
|
||||||
{
|
{
|
||||||
if (control_paragraphs.size() == 0)
|
if (control_paragraphs.size() == 0)
|
||||||
{
|
{
|
||||||
auto ret = std::make_unique<Parse::ParseControlErrorInfo>();
|
auto ret = std::make_unique<Parse::ParseControlErrorInfo>();
|
||||||
ret->name = fs::u8string(path_to_control);
|
ret->name = origin;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto control_file = std::make_unique<SourceControlFile>();
|
auto control_file = std::make_unique<SourceControlFile>();
|
||||||
|
|
||||||
auto maybe_source = parse_source_paragraph(path_to_control, std::move(control_paragraphs.front()));
|
auto maybe_source = parse_source_paragraph(origin, std::move(control_paragraphs.front()));
|
||||||
if (const auto source = maybe_source.get())
|
if (const auto source = maybe_source.get())
|
||||||
control_file->core_paragraph = std::move(*source);
|
control_file->core_paragraph = std::move(*source);
|
||||||
else
|
else
|
||||||
@ -348,14 +383,17 @@ namespace vcpkg
|
|||||||
|
|
||||||
for (auto&& feature_pgh : control_paragraphs)
|
for (auto&& feature_pgh : control_paragraphs)
|
||||||
{
|
{
|
||||||
auto maybe_feature = parse_feature_paragraph(path_to_control, std::move(feature_pgh));
|
auto maybe_feature = parse_feature_paragraph(origin, std::move(feature_pgh));
|
||||||
if (const auto feature = maybe_feature.get())
|
if (const auto feature = maybe_feature.get())
|
||||||
control_file->feature_paragraphs.emplace_back(std::move(*feature));
|
control_file->feature_paragraphs.emplace_back(std::move(*feature));
|
||||||
else
|
else
|
||||||
return std::move(maybe_feature).error();
|
return std::move(maybe_feature).error();
|
||||||
}
|
}
|
||||||
|
|
||||||
canonicalize(*control_file);
|
if (auto maybe_error = canonicalize(*control_file))
|
||||||
|
{
|
||||||
|
return std::move(maybe_error);
|
||||||
|
}
|
||||||
return control_file;
|
return control_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,7 +892,10 @@ namespace vcpkg
|
|||||||
r.optional_object_field(
|
r.optional_object_field(
|
||||||
obj, FEATURES, control_file->feature_paragraphs, FeaturesFieldDeserializer::instance);
|
obj, FEATURES, control_file->feature_paragraphs, FeaturesFieldDeserializer::instance);
|
||||||
|
|
||||||
canonicalize(*control_file);
|
if (auto maybe_error = canonicalize(*control_file))
|
||||||
|
{
|
||||||
|
Checks::exit_with_message(VCPKG_LINE_INFO, maybe_error->error);
|
||||||
|
}
|
||||||
return std::move(control_file);
|
return std::move(control_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -879,8 +920,8 @@ namespace vcpkg
|
|||||||
constexpr StringLiteral ManifestDeserializer::DEFAULT_FEATURES;
|
constexpr StringLiteral ManifestDeserializer::DEFAULT_FEATURES;
|
||||||
constexpr StringLiteral ManifestDeserializer::SUPPORTS;
|
constexpr StringLiteral ManifestDeserializer::SUPPORTS;
|
||||||
|
|
||||||
Parse::ParseExpected<SourceControlFile> SourceControlFile::parse_manifest_file(const fs::path& path_to_manifest,
|
Parse::ParseExpected<SourceControlFile> SourceControlFile::parse_manifest_object(const std::string& origin,
|
||||||
const Json::Object& manifest)
|
const Json::Object& manifest)
|
||||||
{
|
{
|
||||||
Json::Reader reader;
|
Json::Reader reader;
|
||||||
|
|
||||||
@ -889,7 +930,7 @@ namespace vcpkg
|
|||||||
if (!reader.errors().empty())
|
if (!reader.errors().empty())
|
||||||
{
|
{
|
||||||
auto err = std::make_unique<ParseControlErrorInfo>();
|
auto err = std::make_unique<ParseControlErrorInfo>();
|
||||||
err->name = fs::u8string(path_to_manifest);
|
err->name = origin;
|
||||||
err->other_errors = std::move(reader.errors());
|
err->other_errors = std::move(reader.errors());
|
||||||
return std::move(err);
|
return std::move(err);
|
||||||
}
|
}
|
||||||
@ -903,6 +944,12 @@ namespace vcpkg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Parse::ParseExpected<SourceControlFile> SourceControlFile::parse_manifest_file(const fs::path& path_to_manifest,
|
||||||
|
const Json::Object& manifest)
|
||||||
|
{
|
||||||
|
return parse_manifest_object(fs::u8string(path_to_manifest), manifest);
|
||||||
|
}
|
||||||
|
|
||||||
void print_error_message(Span<const std::unique_ptr<Parse::ParseControlErrorInfo>> error_info_list)
|
void print_error_message(Span<const std::unique_ptr<Parse::ParseControlErrorInfo>> error_info_list)
|
||||||
{
|
{
|
||||||
Checks::check_exit(VCPKG_LINE_INFO, error_info_list.size() > 0);
|
Checks::check_exit(VCPKG_LINE_INFO, error_info_list.size() > 0);
|
||||||
|
@ -267,6 +267,7 @@
|
|||||||
<ClInclude Include="..\include\vcpkg\vcpkgcmdarguments.h" />
|
<ClInclude Include="..\include\vcpkg\vcpkgcmdarguments.h" />
|
||||||
<ClInclude Include="..\include\vcpkg\vcpkglib.h" />
|
<ClInclude Include="..\include\vcpkg\vcpkglib.h" />
|
||||||
<ClInclude Include="..\include\vcpkg\vcpkgpaths.h" />
|
<ClInclude Include="..\include\vcpkg\vcpkgpaths.h" />
|
||||||
|
<ClInclude Include="..\include\vcpkg\versions.h" />
|
||||||
<ClInclude Include="..\include\vcpkg\versiont.h" />
|
<ClInclude Include="..\include\vcpkg\versiont.h" />
|
||||||
<ClInclude Include="..\include\vcpkg\visualstudio.h" />
|
<ClInclude Include="..\include\vcpkg\visualstudio.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user