diff --git a/CMakeLists.txt b/CMakeLists.txt index a286795e67..bf83f51ae6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,3 +86,9 @@ if(BUILD_TESTING) enable_testing() add_subdirectory(tests) endif() + +add_subdirectory(tools/format EXCLUDE_FROM_ALL) +if(TARGET clang-format-all) + add_custom_target(format) + add_dependencies(format clang-format-all) +endif() diff --git a/azure-devops/create-prdiff.ps1 b/azure-devops/create-prdiff.ps1 new file mode 100644 index 0000000000..4d2c004fa6 --- /dev/null +++ b/azure-devops/create-prdiff.ps1 @@ -0,0 +1,29 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +[CmdletBinding(PositionalBinding = $False)] +Param( + [Parameter(Mandatory = $True)] + [String]$DiffFile +) + +Start-Process -FilePath 'git' -ArgumentList 'diff' ` + -NoNewWindow -Wait ` + -RedirectStandardOutput $DiffFile +if (0 -ne (Get-Item -LiteralPath $DiffFile).Length) { + $message = @( + '##vso[task.logissue type=error]The formatting of the files in the repo was not what we expected.' + 'Please access the diff from format.diff in the build artifacts' + '(you can download it by clicking the three dots at the right)' + 'and apply it with `git apply`.' + 'Alternatively, you can run the `format` CMake target:' + ' cmake --build --target format' + '' + '##[group]Expected formatting - click to expand diff' + Get-Content -LiteralPath $DiffFile -Raw + '##[endgroup]' + "##vso[artifact.upload artifactname=format.diff]$DiffFile" + '##vso[task.complete result=Failed]DONE' + ) + Write-Host ($message -join "`n") +} diff --git a/azure-devops/enforce-clang-format.cmd b/azure-devops/enforce-clang-format.cmd deleted file mode 100644 index f2d666a663..0000000000 --- a/azure-devops/enforce-clang-format.cmd +++ /dev/null @@ -1,14 +0,0 @@ -:: Copyright (c) Microsoft Corporation. -:: SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -call "%ProgramFiles%\Microsoft Visual Studio\2022\Preview\Common7\Tools\VsDevCmd.bat" ^ --host_arch=amd64 -arch=amd64 -no_logo -"%1" "clang-format.exe -style=file -i" ^ -stl/inc ^ -stl/src ^ -tests ^ -tools -@echo If your build fails here, you need to format the following files with: -@clang-format.exe --version -@git status --porcelain stl tests tools 1>&2 -@echo clang-format will produce the following diff: -@git diff stl tests tools 1>&2 diff --git a/azure-devops/validate-files.cmd b/azure-devops/validate-files.cmd deleted file mode 100644 index e2f53c10f8..0000000000 --- a/azure-devops/validate-files.cmd +++ /dev/null @@ -1,4 +0,0 @@ -:: Copyright (c) Microsoft Corporation. -:: SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -"%1" -@echo If your build fails here, you need to fix the listed issues. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 979f8ed76c..d7c30ec76f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,7 +17,8 @@ stages: timeoutInMinutes: 90 displayName: 'Validation' variables: - buildOutputLocation: 'D:\tools' + - name: DiffFile + value: '$(Build.ArtifactStagingDirectory)/format.diff' steps: - script: | if exist "$(tmpDir)" ( @@ -29,34 +30,32 @@ stages: clean: true submodules: false - script: | - if exist "$(buildOutputLocation)" ( - rmdir /S /Q "$(buildOutputLocation)" - ) call "%ProgramFiles%\Microsoft Visual Studio\2022\Preview\Common7\Tools\VsDevCmd.bat" ^ - -host_arch=amd64 -arch=amd64 -no_logo - cmake -G Ninja -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release ^ - -S $(Build.SourcesDirectory)\tools -B $(buildOutputLocation) - cmake --build $(buildOutputLocation) - displayName: 'Build Support Tools' - env: { TMP: $(tmpDir), TEMP: $(tmpDir) } - - task: BatchScript@1 - displayName: 'Enforce clang-format' - timeoutInMinutes: 60 + -host_arch=amd64 -arch=amd64 -no_logo + cmake -G Ninja -S $(Build.SourcesDirectory)/tools/format -B $(tmpDir)/format-build + cmake --build $(tmpDir)/format-build + displayName: 'clang-format' + timeoutInMinutes: 5 condition: succeededOrFailed() - inputs: - filename: 'azure-devops/enforce-clang-format.cmd' - failOnStandardError: true - arguments: '$(buildOutputLocation)/parallelize/parallelize.exe' env: { TMP: $(tmpDir), TEMP: $(tmpDir) } - - task: BatchScript@1 + - script: | + call "%ProgramFiles%\Microsoft Visual Studio\2022\Preview\Common7\Tools\VsDevCmd.bat" ^ + -host_arch=amd64 -arch=amd64 -no_logo + cmake -G Ninja -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Release ^ + -S $(Build.SourcesDirectory)/tools/validate -B $(tmpDir)/validate-build + cmake --build $(tmpDir)/validate-build + "$(tmpDir)\validate-build\validate.exe" displayName: 'Validate Files' - timeoutInMinutes: 2 + timeoutInMinutes: 5 condition: succeededOrFailed() - inputs: - filename: 'azure-devops/validate-files.cmd' - failOnStandardError: true - arguments: '$(buildOutputLocation)/validate/validate.exe' env: { TMP: $(tmpDir), TEMP: $(tmpDir) } + - task: Powershell@2 + displayName: 'Create Diff' + inputs: + filePath: azure-devops/create-prdiff.ps1 + arguments: '-DiffFile $(DiffFile)' + pwsh: false + condition: succeededOrFailed() - stage: Build_And_Test_x64 dependsOn: Code_Format diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 56d80e3d36..5d4089779e 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -13,6 +13,6 @@ add_compile_options(/W4 /WX $<$>:/Zi> /permissive-) include_directories(inc) +add_subdirectory(format) add_subdirectory(jobify) -add_subdirectory(parallelize) add_subdirectory(validate) diff --git a/tools/format/CMakeLists.txt b/tools/format/CMakeLists.txt new file mode 100644 index 0000000000..70d35bbd25 --- /dev/null +++ b/tools/format/CMakeLists.txt @@ -0,0 +1,71 @@ +# Copyright (c) Microsoft Corporation. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required(VERSION 3.22) +project(msvc_standard_libraries_format NONE) + +set(did_search OFF) +if(NOT DEFINED CLANG_FORMAT) + message(STATUS "Searching for VS clang-format") + set(did_search ON) +endif() + +if(PROJECT_IS_TOP_LEVEL) + set(message_level FATAL_ERROR) +else() + set(message_level WARNING) +endif() + +find_program(CLANG_FORMAT + NAMES clang-format + PATHS "C:/Program Files/Microsoft Visual Studio/2022/Preview/VC/Tools/Llvm/bin" + DOC "The clang-format program to use" + NO_DEFAULT_PATH + NO_CMAKE_PATH + NO_CMAKE_ENVIRONMENT_PATH + NO_SYSTEM_ENVIRONMENT_PATH + NO_CMAKE_SYSTEM_PATH +) +if(CLANG_FORMAT) + if(did_search) + message(STATUS "Searching for VS clang-format - found") + endif() + + file(GLOB_RECURSE maybe_clang_format_files + "../../stl/inc/*" + "../../stl/src/*" + "../../tests/*" + "../../tools/*" + ) + set(clang_format_files "") + foreach(maybe_file IN LISTS maybe_clang_format_files) + cmake_path(GET maybe_file FILENAME filename) + cmake_path(GET maybe_file EXTENSION LAST_ONLY extension) + if(extension MATCHES [[^(|\.cpp|\.h|\.hpp)$]] AND NOT filename MATCHES [[^\.]]) + list(APPEND clang_format_files "${maybe_file}") + endif() + endforeach() + + if(NOT clang_format_files) + message("${message_level}" "Could not find any files to clang-format!") + endif() + + add_custom_target(clang-format-all ALL) + foreach(file IN LISTS clang_format_files) + cmake_path(RELATIVE_PATH file + BASE_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../.." + OUTPUT_VARIABLE relative-file + ) + string(REPLACE "/" "_" relative-file "${relative-file}") + set(target_name "clang-format.${relative-file}") + add_custom_target("${target_name}" + COMMAND "${CLANG_FORMAT}" -style=file -i "${file}" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + ) + add_dependencies(clang-format-all "${target_name}") + endforeach() +else() + if(did_search) + message("${message_level}" "Searching for VS clang-format - not found.") + endif() +endif() diff --git a/tools/parallelize/CMakeLists.txt b/tools/parallelize/CMakeLists.txt deleted file mode 100644 index 348124b456..0000000000 --- a/tools/parallelize/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -add_executable(parallelize parallelize.cpp ../inc/stljobs.h) -target_link_libraries(parallelize Userenv.lib) diff --git a/tools/parallelize/parallelize.cpp b/tools/parallelize/parallelize.cpp deleted file mode 100644 index 87cee625ee..0000000000 --- a/tools/parallelize/parallelize.cpp +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception - -#include "stljobs.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -class tp_wait { -public: - explicit tp_wait(PTP_WAIT_CALLBACK pfnwa, PVOID pv, PTP_CALLBACK_ENVIRON pcbe) - : wait(CreateThreadpoolWait(pfnwa, pv, pcbe)) { - if (!wait) { - api_failure("CreateThreadpoolWait"); - } - } - - tp_wait(const tp_wait&) = delete; - tp_wait& operator=(const tp_wait&) = delete; - - void wait_for(const HANDLE waitOn) noexcept { - SetThreadpoolWait(wait, waitOn, nullptr); - } - - ~tp_wait() { - WaitForThreadpoolWaitCallbacks(wait, TRUE); - CloseThreadpoolWait(wait); - } - -private: - PTP_WAIT wait{}; -}; - -class parallelizer { -public: - parallelizer() : subprocesses(std::make_unique(availableConcurrency)) { - for (size_t idx = 0; idx < availableConcurrency; ++idx) { - subprocesses[idx].parent = this; - } - } - - void add_command(std::wstring&& toRun) { - std::lock_guard lck(mtx); - const auto oldCommandsSize = commands.size(); - commands.emplace_back(std::move(toRun), std::nullopt); - const auto localConcurrency = runningConcurrency.load(std::memory_order_relaxed); - if (availableConcurrency == localConcurrency) { - return; - } - - runningConcurrency.store(localConcurrency + 1, std::memory_order_relaxed); - size_t scheduledOn = 0; - while (subprocesses[scheduledOn].commandRunning != SIZE_MAX) { - ++scheduledOn; - } - - nextCommandToRun = oldCommandsSize + 1; - subprocesses[scheduledOn].schedule(oldCommandsSize); - update_display(); - } - - void wait_all() noexcept { - std::unique_lock lck{mtx}; - cv.wait(lck, [this] { return runningConcurrency.load(std::memory_order_relaxed) == 0; }); - } - - [[nodiscard]] const std::vector>>& - results() const noexcept { - assert(runningConcurrency.load(std::memory_order_relaxed) == 0); - return commands; - } - -private: - struct entry; - std::mutex mtx{}; - std::condition_variable cv{}; - std::vector>> commands{}; - size_t nextCommandToRun{0}; - size_t availableConcurrency{std::thread::hardware_concurrency()}; - std::atomic runningConcurrency{0}; - std::unique_ptr subprocesses; - - struct entry { - parallelizer* parent; - size_t commandRunning{SIZE_MAX}; // guarded by parent->mtx - subprocess_executive executive; - tp_wait tpWait{callback, this, nullptr}; - - static void __stdcall callback( - PTP_CALLBACK_INSTANCE, void* thisRaw, PTP_WAIT, [[maybe_unused]] TP_WAIT_RESULT waitDisposition) noexcept { - assert(waitDisposition == WAIT_OBJECT_0); - const auto this_ = static_cast(thisRaw); - const auto parent = this_->parent; - auto results = this_->executive.complete(); - std::lock_guard lck{parent->mtx}; - parent->commands[this_->commandRunning].second.emplace(std::move(results)); - if (parent->nextCommandToRun == parent->commands.size()) { - this_->commandRunning = SIZE_MAX; - if (parent->runningConcurrency.fetch_sub(1, std::memory_order_relaxed) == 1) { - parent->cv.notify_all(); - } - } else { - this_->schedule(parent->nextCommandToRun++); - } - - parent->update_display(); - } - - void schedule(const size_t command) { - commandRunning = command; - executive.begin_execution(nullptr, parent->commands[command].first.data(), 0, nullptr); - tpWait.wait_for(executive.get_wait_handle()); - } - }; - - void update_display() noexcept { - const auto commandsSize = commands.size(); - const auto next = nextCommandToRun; - const auto running = runningConcurrency.load(std::memory_order_relaxed); - assert(running <= next); - printf("%zu scheduled; %zu completed; %zu running\n", commandsSize, next - running, running); - } -}; - -void schedule_command(parallelizer& p, const std::wstring_view commandPrefix, const std::wstring& native) { - std::wstring toExecute; - toExecute.reserve(commandPrefix.size() + 1 + native.size()); - toExecute.assign(commandPrefix); - toExecute.push_back(L' '); - toExecute.append(native); - p.add_command(std::move(toExecute)); -} - -extern "C" int wmain(int argc, wchar_t* argv[]) { - try { - using namespace std::string_view_literals; - - static constexpr std::array accepted_extensions{ - L""sv, // extensionless headers like - L".cpp"sv, - L".h"sv, - L".hpp"sv, - }; - - static_assert(std::is_sorted(accepted_extensions.begin(), accepted_extensions.end())); - - if (argc < 3) { - puts("Usage: parallelize.exe commandPrefix pathRoot0 [... pathRootN]\n" - "The command:\n" - "commandPrefix file\n" - "will be launched in parallel for regular files under any of pathRoots, recursively."); - printf("Accepted extensions: "); - for (const auto& sv : accepted_extensions) { - printf("\"%.*ls\", ", static_cast(sv.size()), sv.data()); - } - printf("\n"); - puts("Other extensions will be skipped."); - return 1; - } - - parallelizer p; - std::wstring_view commandPrefix(argv[1]); - for (int idx = 2; idx < argc; ++idx) { - std::filesystem::path thisSpec(argv[idx]); - if (std::filesystem::is_regular_file(thisSpec)) { - schedule_command(p, commandPrefix, thisSpec.native()); - continue; - } - - for (const auto& dirEntry : std::filesystem::recursive_directory_iterator(std::move(thisSpec))) { - if (!dirEntry.is_regular_file()) { - printf("Skipping non-regular-file %ls\n", dirEntry.path().c_str()); - continue; - } - - if (!binary_search( - accepted_extensions.begin(), accepted_extensions.end(), dirEntry.path().extension().native())) { - printf("Skipping non-accepted-extension %ls\n", dirEntry.path().c_str()); - continue; - } - - // intentionally reports that dotfiles (like .gitignore) are extensionless. - if (dirEntry.path().stem().native().starts_with(L"."sv)) { - printf("Skipping dotfile %ls\n", dirEntry.path().c_str()); - continue; - } - - schedule_command(p, commandPrefix, dirEntry.path().native()); - } - } - - p.wait_all(); - bool exitSuccess = true; - size_t vacuousCount = 0; - for (auto& command : p.results()) { - auto& result = command.second.value(); - if (result.exitCode == 0) { - if (result.output.empty()) { - ++vacuousCount; - } else { - printf("%ls produced output:\n%s\n", command.first.c_str(), result.output.c_str()); - } - } else { - exitSuccess = false; - if (result.output.empty()) { - printf("%ls exited with 0x%08lX and no output.\n", command.first.c_str(), result.exitCode); - } else { - printf("%ls exited with 0x%08lX and output:\n%s\n", command.first.c_str(), result.exitCode, - result.output.c_str()); - } - } - } - - const auto totalCommands = p.results().size(); - printf("%zu commands ran, returned 0, and produced no output", vacuousCount); - if (vacuousCount == totalCommands) { - puts("."); - } else { - printf(" (out of %zu).", totalCommands); - } - - if (exitSuccess) { - return 0; - } else { - return 1; - } - } catch (std::filesystem::filesystem_error& err) { - fputs(err.what(), stderr); - abort(); - } catch (api_exception& api) { - api.give_up(); - } -} diff --git a/tools/validate/CMakeLists.txt b/tools/validate/CMakeLists.txt index 96fd38dcba..81909e1f39 100644 --- a/tools/validate/CMakeLists.txt +++ b/tools/validate/CMakeLists.txt @@ -1,4 +1,15 @@ # Copyright (c) Microsoft Corporation. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +cmake_minimum_required(VERSION 3.22) +project(msvc_standard_libraries_validate LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +add_compile_definitions(NOMINMAX UNICODE _UNICODE) +# we use SAL annotations, so pass /analyze +add_compile_options(/W4 /WX $<$>:/Zi> /permissive- /analyze) + add_executable(validate validate.cpp) diff --git a/tools/validate/validate.cpp b/tools/validate/validate.cpp index 1491f1f3d1..c590c7dd9e 100644 --- a/tools/validate/validate.cpp +++ b/tools/validate/validate.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -52,9 +53,26 @@ class BinaryFile { FILE* m_file{nullptr}; }; +int validation_failure( + bool& any_errors, const filesystem::path& filepath, _Printf_format_string_ const wchar_t* format, ...) { + any_errors = true; + + fwprintf(stderr, L"##vso[task.logissue type=error;sourcepath=%ls;linenumber=1;columnnumber=1]Validation failed: ", + filepath.c_str()); + + va_list args; + va_start(args, format); + const int result = vfwprintf(stderr, format, args); + va_end(args); + + fwprintf(stderr, L"\n"); + return result; +} + enum class TabPolicy : bool { Forbidden, Allowed }; -void scan_file(const filesystem::path& filepath, const TabPolicy tab_policy, vector& buffer) { +void scan_file( + bool& any_errors, const filesystem::path& filepath, const TabPolicy tab_policy, vector& buffer) { constexpr char CR = '\r'; constexpr char LF = '\n'; @@ -100,8 +118,8 @@ void scan_file(const filesystem::path& filepath, const TabPolicy tab_policy, vec ++disallowed_characters; constexpr size_t MaxErrorsForDisallowedCharacters = 10; if (disallowed_characters <= MaxErrorsForDisallowedCharacters) { - fwprintf(stderr, L"Validation failed: %ls contains disallowed character 0x%02X.\n", - filepath.c_str(), static_cast(ch)); + validation_failure(any_errors, filepath, L"file contains disallowed character 0x%02X.", + static_cast(ch)); } } @@ -126,41 +144,38 @@ void scan_file(const filesystem::path& filepath, const TabPolicy tab_policy, vec } if (has_cr) { - fwprintf( - stderr, L"Validation failed: %ls contains CR line endings (possibly damaged CRLF).\n", filepath.c_str()); + validation_failure(any_errors, filepath, L"file contains CR line endings (possibly damaged CRLF)."); } else if (has_lf && has_crlf) { - fwprintf(stderr, L"Validation failed: %ls contains mixed line endings (both LF and CRLF).\n", filepath.c_str()); + validation_failure(any_errors, filepath, L"file contains mixed line endings (both LF and CRLF)."); } else if (has_lf) { - fwprintf(stderr, L"Validation failed: %ls contains LF line endings.", filepath.c_str()); + validation_failure(any_errors, filepath, L"file contains LF line endings."); if (prev != LF) { - fwprintf(stderr, L" Also, it doesn't end with a newline.\n"); + validation_failure(any_errors, filepath, L"file doesn't end with a newline."); } else if (previous2 == LF) { - fwprintf(stderr, L" Also, it ends with multiple newlines.\n"); - } else { - fwprintf(stderr, L"\n"); + validation_failure(any_errors, filepath, L"file ends with multiple newlines."); } } else if (has_crlf) { if (previous2 != CR || prev != LF) { - fwprintf(stderr, L"Validation failed: %ls doesn't end with a newline.\n", filepath.c_str()); + validation_failure(any_errors, filepath, L"file doesn't end with a newline."); } else if (previous3 == LF) { - fwprintf(stderr, L"Validation failed: %ls ends with multiple newlines.\n", filepath.c_str()); + validation_failure(any_errors, filepath, L"file ends with multiple newlines."); } } else { - fwprintf(stderr, L"Validation failed: %ls doesn't contain any newlines.\n", filepath.c_str()); + validation_failure(any_errors, filepath, L"file doesn't contain any newlines."); } if (has_utf8_bom) { - fwprintf(stderr, L"Validation failed: %ls contains UTF-8 BOM characters.\n", filepath.c_str()); + validation_failure(any_errors, filepath, L"file contains UTF-8 BOM characters."); } if (tab_policy == TabPolicy::Forbidden && tab_characters != 0) { - fwprintf(stderr, L"Validation failed: %ls contains %zu tab characters.\n", filepath.c_str(), tab_characters); + validation_failure(any_errors, filepath, L"file contains %zu tab characters.", tab_characters); } if (trailing_whitespace_lines != 0) { - fwprintf(stderr, L"Validation failed: %ls contains %zu lines with trailing whitespace.\n", filepath.c_str(), - trailing_whitespace_lines); + validation_failure( + any_errors, filepath, L"file contains %zu lines with trailing whitespace.", trailing_whitespace_lines); } if (overlength_lines != 0) { @@ -179,8 +194,8 @@ void scan_file(const filesystem::path& filepath, const TabPolicy tab_policy, vec static_assert(is_sorted(checked_extensions.begin(), checked_extensions.end())); if (binary_search(checked_extensions.begin(), checked_extensions.end(), filepath.extension().wstring())) { - fwprintf(stderr, L"Validation failed: %ls contains %zu lines with more than %zu columns.\n", - filepath.c_str(), overlength_lines, max_line_length); + validation_failure(any_errors, filepath, L"file contains %zu lines with more than %zu columns.\n", + overlength_lines, max_line_length); } } } @@ -203,15 +218,23 @@ int main() { L".obj"sv, }; + // make sure someone doesn't accidentally include a diff in the tree + static constexpr array bad_extensions{ + L".diff"sv, + L".patch"sv, + }; + static constexpr array tabby_filenames{ L".gitmodules"sv, }; static_assert(is_sorted(skipped_directories.begin(), skipped_directories.end())); static_assert(is_sorted(skipped_extensions.begin(), skipped_extensions.end())); + static_assert(is_sorted(bad_extensions.begin(), bad_extensions.end())); static_assert(is_sorted(tabby_filenames.begin(), tabby_filenames.end())); vector buffer; // reused for performance + bool any_errors = false; for (filesystem::recursive_directory_iterator rdi{"."}, last; rdi != last; ++rdi) { const filesystem::path& filepath = rdi->path(); @@ -232,12 +255,12 @@ int main() { constexpr size_t maximum_relative_path_length = 120; if (relative_path.size() > maximum_relative_path_length) { - fwprintf(stderr, L"Validation failed: the path \"%ls\" is too long (%zu characters; the limit is %zu).\n", - filepath.c_str(), relative_path.size(), maximum_relative_path_length); + validation_failure(any_errors, filepath, L"filepath is too long (%zu characters; the limit is %zu).", + relative_path.size(), maximum_relative_path_length); } if (relative_path.find(L' ') != wstring::npos) { - fwprintf(stderr, L"Validation failed: the path \"%ls\" contains spaces.\n", filepath.c_str()); + validation_failure(any_errors, filepath, L"filepath contains spaces."); } const wstring extension = filepath.extension().wstring(); @@ -246,10 +269,21 @@ int main() { continue; } + if (binary_search(bad_extensions.begin(), bad_extensions.end(), extension)) { + validation_failure(any_errors, filepath, L"file should not be checked in."); + continue; + } + const TabPolicy tab_policy = binary_search(tabby_filenames.begin(), tabby_filenames.end(), filename) ? TabPolicy::Allowed : TabPolicy::Forbidden; - scan_file(filepath, tab_policy, buffer); + scan_file(any_errors, filepath, tab_policy, buffer); + } + + if (any_errors) { + fwprintf( + stderr, L"##vso[task.logissue type=warning]If your build fails here, you need to fix the listed issues.\n"); + fwprintf(stderr, L"##vso[task.complete result=Failed]DONE\n"); } }