diff --git a/azure-pipelines/e2e-assets/ci/ci-baseline-test.txt b/azure-pipelines/e2e-assets/ci/ci-baseline-test.txt new file mode 100644 index 0000000000..41beb32222 --- /dev/null +++ b/azure-pipelines/e2e-assets/ci/ci-baseline-test.txt @@ -0,0 +1,9 @@ +# CI Baseline for testing early filtering +# skip-dep should be skipped on all triplets +skip-dep:x86-windows=skip +skip-dep:x64-windows=skip +skip-dep:arm64-windows=skip +skip-dep:x64-linux=skip +skip-dep:arm64-linux=skip +skip-dep:x64-osx=skip +skip-dep:arm64-osx=skip diff --git a/azure-pipelines/e2e-ports/ci-feature-baseline/base-dep/portfile.cmake b/azure-pipelines/e2e-ports/ci-feature-baseline/base-dep/portfile.cmake new file mode 100644 index 0000000000..719f690e76 --- /dev/null +++ b/azure-pipelines/e2e-ports/ci-feature-baseline/base-dep/portfile.cmake @@ -0,0 +1,7 @@ +vcpkg_minimum_required(VERSION 2024-11-01) + +message(STATUS "Installing base-dep") + +file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" "Test port") + +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/azure-pipelines/e2e-ports/ci-feature-baseline/base-dep/vcpkg.json b/azure-pipelines/e2e-ports/ci-feature-baseline/base-dep/vcpkg.json new file mode 100644 index 0000000000..09a33c279c --- /dev/null +++ b/azure-pipelines/e2e-ports/ci-feature-baseline/base-dep/vcpkg.json @@ -0,0 +1,10 @@ +{ + "name": "base-dep", + "version": "1.0.0", + "description": "Test port with optional feature that should be skipped", + "features": { + "feature-to-skip": { + "description": "Optional feature (should be skipped when skip-dep is excluded)" + } + } +} diff --git a/azure-pipelines/e2e-ports/ci-feature-baseline/skip-dep/portfile.cmake b/azure-pipelines/e2e-ports/ci-feature-baseline/skip-dep/portfile.cmake new file mode 100644 index 0000000000..df242f3acd --- /dev/null +++ b/azure-pipelines/e2e-ports/ci-feature-baseline/skip-dep/portfile.cmake @@ -0,0 +1,7 @@ +vcpkg_minimum_required(VERSION 2024-11-01) + +message(STATUS "Installing skip-dep") + +file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/copyright" "Test port") + +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/azure-pipelines/e2e-ports/ci-feature-baseline/skip-dep/vcpkg.json b/azure-pipelines/e2e-ports/ci-feature-baseline/skip-dep/vcpkg.json new file mode 100644 index 0000000000..9df0579c60 --- /dev/null +++ b/azure-pipelines/e2e-ports/ci-feature-baseline/skip-dep/vcpkg.json @@ -0,0 +1,11 @@ +{ + "name": "skip-dep", + "version": "1.0.0", + "description": "Test port that depends on base-dep[feature-to-skip]", + "dependencies": [ + { + "name": "base-dep", + "features": ["feature-to-skip"] + } + ] +} diff --git a/azure-pipelines/end-to-end-tests-dir/ci.ps1 b/azure-pipelines/end-to-end-tests-dir/ci.ps1 index 7b1b38d0d2..d43749235a 100644 --- a/azure-pipelines/end-to-end-tests-dir/ci.ps1 +++ b/azure-pipelines/end-to-end-tests-dir/ci.ps1 @@ -56,3 +56,31 @@ New-Item -ItemType Directory -Path $emptyDir -Force | Out-Null $Output = Run-VcpkgAndCaptureOutput ci --triplet=$Triplet --x-builtin-ports-root="$emptyDir" --binarysource=clear --overlay-ports="$PSScriptRoot/../e2e-ports/duplicate-file-a" --overlay-ports="$PSScriptRoot/../e2e-ports/duplicate-file-b" Throw-IfNotFailed Restore-Problem-Matchers + +# Test CI baseline early filtering functionality +# Test that ports marked as skip in ci.baseline.txt are excluded early from CI +$Output = Run-VcpkgAndCaptureOutput ci --dry-run --triplet=$Triplet --x-builtin-ports-root="$PSScriptRoot/../e2e-ports/ci-feature-baseline" --binarysource=clear --ci-baseline="$PSScriptRoot/../e2e-assets/ci/ci-baseline-test.txt" +Throw-IfFailed +# skip-dep should be marked as skip in the output (verifies it appears AND has skip status) +if (-not ($Output -match "skip-dep:${Triplet}:\s+skip:")) { + throw 'skip-dep should be marked as skip in ci.baseline.txt and appear in output' +} +# base-dep should be in the installation list (not skipped) +if (-not ($Output -match "base-dep:${Triplet}@")) { + throw 'base-dep should be in the installation list' +} +# skip-dep should NOT be in the installation list +if ($Output -match "skip-dep:${Triplet}@") { + throw 'skip-dep should NOT be in the installation list (marked as skip)' +} + +# Test that without CI baseline, both ports are included +$Output2 = Run-VcpkgAndCaptureOutput ci --dry-run --triplet=$Triplet --x-builtin-ports-root="$PSScriptRoot/../e2e-ports/ci-feature-baseline" --binarysource=clear +Throw-IfFailed +# Both ports should be in the installation list (may have features like [core,feature-to-skip]) +if (-not ($Output2 -match "base-dep(\[.+?\])?:${Triplet}@")) { + throw 'base-dep should be in the installation list without baseline' +} +if (-not ($Output2 -match "skip-dep(\[.+?\])?:${Triplet}@")) { + throw 'skip-dep should be in the installation list without baseline' +} diff --git a/src/vcpkg/commands.ci.cpp b/src/vcpkg/commands.ci.cpp index 63d975cf8c..13bb85c011 100644 --- a/src/vcpkg/commands.ci.cpp +++ b/src/vcpkg/commands.ci.cpp @@ -31,6 +31,8 @@ using namespace vcpkg; namespace { + constexpr StringLiteral dummy_abi = "0000000000000000000000000000000000000000000000000000000000000000"; + constexpr CommandSetting CI_SETTINGS[] = { {SwitchExclude, msgCISettingsOptExclude}, {SwitchHostExclude, msgCISettingsOptHostExclude}, @@ -59,6 +61,43 @@ namespace std::vector action_state_string; }; + // Build a set of package specs to exclude based on the CI baseline exclusions map for the target triplet + SortedVector calculate_ci_excluded_specs(const ExclusionsMap& exclusions_map, Triplet target_triplet) + { + std::vector exclude_list; + for (const auto& triplet_exclusions : exclusions_map.triplets) + { + if (triplet_exclusions.triplet == target_triplet) + { + for (const auto& port_name : triplet_exclusions.exclusions) + { + exclude_list.emplace_back(port_name, target_triplet); + } + break; + } + } + return SortedVector(std::move(exclude_list)); + } + + // Build the list of package specs to install for CI (all ports with default features, excluding those in the + // exclusion list) + std::vector calculate_ci_requested_specs(const PortFileProvider& provider, + Triplet target_triplet, + const SortedVector& ports_to_exclude) + { + std::vector ci_requested_default_full_specs; + for (auto scfl : provider.load_all_control_files()) + { + PackageSpec pkg_spec{scfl->to_name(), target_triplet}; + if (!ports_to_exclude.contains(pkg_spec)) + { + ci_requested_default_full_specs.emplace_back( + pkg_spec, InternalFeatureSet{FeatureNameCore.to_string(), FeatureNameDefault.to_string()}); + } + } + return ci_requested_default_full_specs; + } + bool supported_for_triplet(const CMakeVars::CMakeVarProvider& var_provider, const SourceControlFile& source_control_file, PackageSpec spec) @@ -353,18 +392,16 @@ namespace vcpkg auto registry_set = paths.make_registry_set(); PathsPortFileProvider provider(*registry_set, make_overlay_provider(fs, paths.overlay_ports)); + + // Build a set of ports to exclude based on the exclusions_map for the target triplet + auto ports_to_exclude = calculate_ci_excluded_specs(exclusions_map, target_triplet); + auto var_provider_storage = CMakeVars::make_triplet_cmake_var_provider(paths); auto& var_provider = *var_provider_storage; const ElapsedTimer timer; // Install the default features for every package - std::vector all_default_full_specs; - for (auto scfl : provider.load_all_control_files()) - { - all_default_full_specs.emplace_back( - PackageSpec{scfl->to_name(), target_triplet}, - InternalFeatureSet{FeatureNameCore.to_string(), FeatureNameDefault.to_string()}); - } + auto ci_requested_default_full_specs = calculate_ci_requested_specs(provider, target_triplet, ports_to_exclude); struct RandomizerInstance : GraphRandomizer { @@ -386,8 +423,12 @@ namespace vcpkg PackagesDirAssigner packages_dir_assigner{paths.packages()}; CreateInstallPlanOptions create_install_plan_options( randomizer, host_triplet, UnsupportedPortAction::Warn, UseHeadVersion::No, Editable::No); - auto action_plan = compute_full_plan( - paths, provider, var_provider, all_default_full_specs, packages_dir_assigner, create_install_plan_options); + auto action_plan = compute_full_plan(paths, + provider, + var_provider, + ci_requested_default_full_specs, + packages_dir_assigner, + create_install_plan_options); BinaryCache binary_cache(fs); if (!binary_cache.install_providers(args, paths, out_sink)) { @@ -400,7 +441,17 @@ namespace vcpkg LocalizedString not_supported_regressions; { std::string msg; - for (const auto& spec : all_default_full_specs) + + // First, print ports that were excluded early (before dependency resolution) + for (const auto& excluded_spec : ports_to_exclude) + { + split_specs->known.emplace(excluded_spec, BuildResult::Excluded); + split_specs->abi_map.emplace(excluded_spec, dummy_abi); + split_specs->features.emplace(excluded_spec, std::vector{"core"}); + msg += fmt::format("{:>40}: {:>8}: {}\n", excluded_spec, "skip", dummy_abi); + } + + for (const auto& spec : ci_requested_default_full_specs) { if (!Util::Sets::contains(split_specs->abi_map, spec.package_spec)) {