diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e01009554..a354cb288 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,6 +77,59 @@ jobs: - run: pytest -x -s working-directory: objdir + CI-windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + python-version: '3.12' + activate-environment: cvise-env + - name: Create DIA SDK junction + shell: cmd + run: | + setlocal enabledelayedexpansion + set "CURR_DRIVE=%CD:~0,2%" + for /d %%i in ("C:\Program Files\Microsoft Visual Studio\2022\Enterprise", "C:\Program Files\Microsoft Visual Studio\2022\Community", "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise", "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community") do ( + if exist "%%~i\DIA SDK" ( + if exist "C:\DIA SDK" rmdir "C:\DIA SDK" + mklink /J "C:\DIA SDK" "%%~i\DIA SDK" + if /i NOT "!CURR_DRIVE!"=="C:" ( + if exist "!CURR_DRIVE!\DIA SDK" rmdir "!CURR_DRIVE!\DIA SDK" + mklink /J "!CURR_DRIVE!\DIA SDK" "%%~i\DIA SDK" + ) + goto :done + ) + ) + :done + - name: Install dependencies + shell: bash -l {0} + run: | + conda install -y -c conda-forge llvmdev clangdev winflexbison \ + chardet jsonschema msgspec pebble psutil pytest pytest-mock pytest-subprocess zstandard + - name: Configure + shell: bash -l {0} + run: | + CONDA_PREFIX_UNIX=$(cygpath -m "$CONDA_PREFIX") + cmake -S . -B objdir \ + -DCMAKE_PREFIX_PATH="$CONDA_PREFIX_UNIX/Library" \ + -DLLVM_DIR="$CONDA_PREFIX_UNIX/Library/lib/cmake/llvm" \ + -DClang_DIR="$CONDA_PREFIX_UNIX/Library/lib/cmake/clang" \ + -DFLEX_EXECUTABLE="$CONDA_PREFIX_UNIX/Library/bin/win_flex.exe" \ + -DPython3_EXECUTABLE="$CONDA_PREFIX_UNIX/python.exe" \ + -DCLANG_FORMAT_PATH="$CONDA_PREFIX_UNIX/Library/bin/clang-format.exe" + - name: Build + shell: bash -l {0} + run: cmake --build objdir --config Release --parallel $NUMBER_OF_PROCESSORS + - name: Test + shell: bash -l {0} + run: | + cd objdir + pytest -x -s + CI-python: runs-on: ubuntu-latest container: diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e768c8f0..c81fa7b21 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -221,14 +221,14 @@ add_subdirectory(clang_include_graph) add_subdirectory(clex) add_subdirectory(cvise) add_subdirectory(delta) -add_subdirectory(treesitter_delta) +# add_subdirectory(treesitter_delta) # Always link statically against Tree-sitter. -set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") -set(BUILD_SHARED_LIBS OFF) -add_subdirectory(tree-sitter EXCLUDE_FROM_ALL) -add_subdirectory(tree-sitter-cpp EXCLUDE_FROM_ALL) -set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}") +# set(BUILD_SHARED_LIBS_SAVED "${BUILD_SHARED_LIBS}") +# set(BUILD_SHARED_LIBS OFF) +# add_subdirectory(tree-sitter EXCLUDE_FROM_ALL) +# add_subdirectory(tree-sitter-cpp EXCLUDE_FROM_ALL) +# set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}") # Copy top-level cvise script configure_file( diff --git a/clang_delta/CMakeLists.txt b/clang_delta/CMakeLists.txt index c097241ef..676b32238 100644 --- a/clang_delta/CMakeLists.txt +++ b/clang_delta/CMakeLists.txt @@ -24,7 +24,7 @@ function(configure_one_file path) configure_file( "${clang_delta_SOURCE_DIR}/${path}" "${clang_delta_BINARY_DIR}/${path}" - COPYONLY + @ONLY ) endfunction(configure_one_file) @@ -679,7 +679,7 @@ set_target_properties(clang_delta # On Windows, we also need to link with "Version.dll" system library. # See . -if("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") +if(MSVC) target_link_libraries(clang_delta Version) endif() diff --git a/clang_delta/tests/test_clang_delta.py b/clang_delta/tests/test_clang_delta.py index 89bf696a2..c92e06778 100644 --- a/clang_delta/tests/test_clang_delta.py +++ b/clang_delta/tests/test_clang_delta.py @@ -18,9 +18,8 @@ def get_clang_version(): - current = os.path.dirname(__file__) - binary = os.path.join(current, '../clang_delta') - output = subprocess.check_output(f'{binary} --version', shell=True, text=True) + binary = get_clang_delta_path() + output = subprocess.check_output(f'"{binary}" --version', shell=True, text=True) for line in output.splitlines(): m = re.match(r'clang version (?P[0-9]+)\.', line) if m: @@ -30,7 +29,15 @@ def get_clang_version(): def get_clang_delta_path() -> Path: - return Path(__file__).parent.parent / 'clang_delta' + current = Path(__file__).parent.parent.resolve() + binary_name = 'clang_delta' + '@CMAKE_EXECUTABLE_SUFFIX@' + p = current / binary_name + if not p.exists(): + for cfg in ['Release', 'Debug', 'RelWithDebInfo', 'MinSizeRel']: + p_cfg = current / cfg / binary_name + if p_cfg.exists(): + return p_cfg + return p def get_testcase_path(testcase: str) -> Path: @@ -78,18 +85,18 @@ def check_clang_delta_hints( @classmethod def check_query_instances(cls, testcase, arguments, expected): current = os.path.dirname(__file__) - binary = os.path.join(current, '../clang_delta') - cmd = f'{binary} {os.path.join(current, testcase)} {arguments}' + binary = get_clang_delta_path() + cmd = f'"{binary}" {os.path.join(current, testcase)} {arguments}' output = subprocess.check_output(cmd, shell=True, encoding='utf8') assert output.strip() == expected @classmethod def check_error_message(cls, testcase, arguments, error_message): current = os.path.dirname(__file__) - binary = os.path.join(current, '../clang_delta') - cmd = f'{binary} {os.path.join(current, testcase)} {arguments}' + binary = get_clang_delta_path() + cmd = f'"{binary}" {os.path.join(current, testcase)} {arguments}' proc = subprocess.run(cmd, shell=True, encoding='utf8', stdout=subprocess.PIPE) - assert proc.returncode == 255 + assert proc.returncode in (255, 4294967295) assert proc.stdout.strip() == error_message def test_aggregate_to_scalar_cast(self): @@ -1326,9 +1333,9 @@ def test_union_to_struct_union3(self): def test_piggypacking(self): current = os.path.dirname(__file__) - binary = os.path.join(current, '../clang_delta') + binary = get_clang_delta_path() args = '--transformation=remove-unused-function --counter=111 --to-counter=222 --warn-on-counter-out-of-bounds --report-instances-count' - cmd = '{} {} {}'.format(binary, os.path.join(current, 'remove-unused-function/macro2.cc'), args) + cmd = f'"{binary}" {os.path.join(current, "remove-unused-function/macro2.cc")} {args}' run = subprocess.run(cmd, shell=True, encoding='utf8', capture_output=True) assert 'Available transformation instances: 1' in run.stderr assert 'Warning: number of transformation instances exceeded' in run.stderr diff --git a/clex/CMakeLists.txt b/clex/CMakeLists.txt index da4a029ae..8b19aeae2 100644 --- a/clex/CMakeLists.txt +++ b/clex/CMakeLists.txt @@ -66,6 +66,13 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" set_source_files_properties(clex.c PROPERTIES COMPILE_FLAGS "-Wno-unused-function -Wno-sign-compare") set_source_files_properties(strlex.c PROPERTIES COMPILE_FLAGS "-Wno-unused-function -Wno-sign-compare") endif() + +# Serialize flex execution to avoid potential race conditions with temporary files +add_custom_command(OUTPUT ${FLEX_strlex_scanner_OUTPUTS} + APPEND + DEPENDS ${FLEX_clex_scanner_OUTPUTS} +) + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set_source_files_properties(clex.c PROPERTIES COMPILE_FLAGS -DYY_NO_UNISTD_H) set_source_files_properties(strlex.c PROPERTIES COMPILE_FLAGS -DYY_NO_UNISTD_H) diff --git a/cmake_config.h.in b/cmake_config.h.in index 3edf898a6..755c56923 100755 --- a/cmake_config.h.in +++ b/cmake_config.h.in @@ -55,3 +55,38 @@ /* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a `char[]'. */ #cmakedefine YYTEXT_POINTER 1 + +#ifdef _WIN32 +#include +#include +#include +#include +#define isatty _isatty +#define fileno _fileno +#define read _read +#include +typedef SSIZE_T ssize_t; + +static inline ssize_t getdelim(char **lineptr, size_t *n, int delim, FILE *stream) { + if (lineptr == NULL || n == NULL || stream == NULL) return -1; + if (*lineptr == NULL) { + *n = 128; + *lineptr = (char *)malloc(*n); + if (*lineptr == NULL) return -1; + } + int c; + size_t i = 0; + while ((c = getc(stream)) != EOF) { + if (i + 1 >= *n) { + *n *= 2; + char *next = (char *)realloc(*lineptr, *n); + if (next == NULL) return -1; + *lineptr = next; + } + (*lineptr)[i++] = (char)c; + if (c == delim) break; + } + (*lineptr)[i] = '\0'; + return (i == 0 && c == EOF) ? -1 : (ssize_t)i; +} +#endif diff --git a/conftest.py b/conftest.py index cc3bee9b3..a025cc228 100644 --- a/conftest.py +++ b/conftest.py @@ -2,6 +2,8 @@ import multiprocessing +import platform + import pytest @@ -12,4 +14,5 @@ def mp_start_method(): The "forkserver" mode is the same as the one used by the C-Vise CLI. """ # Enforce the method selection, in case the test framework previously set a different one. - multiprocessing.set_start_method('forkserver', force=True) + method = 'spawn' if platform.system() == 'Windows' else 'forkserver' + multiprocessing.set_start_method(method, force=True) diff --git a/cvise/tests/test_balanced.py b/cvise/tests/test_balanced.py index 6548ad8bb..09f466cea 100644 --- a/cvise/tests/test_balanced.py +++ b/cvise/tests/test_balanced.py @@ -78,7 +78,14 @@ def test_parens_dir(self): state = self._pass_new() all_transforms = collect_all_transforms_dir(self.pass_, state, self.input_path) - self.assertIn((('a.txt', b'This is a test!\n'), ('b.txt', b'This \n')), all_transforms) + # Normalize line endings for Windows compatibility + normalized_transforms = set() + for t in all_transforms: + normalized_transforms.add( + tuple((name, content.replace(b'\r\n', b'\n')) for name, content in t) + ) + + self.assertIn((('a.txt', b'This is a test!\n'), ('b.txt', b'This \n')), normalized_transforms) class BalancedParensOnlyTestCase(unittest.TestCase): diff --git a/delta/topformflat.l b/delta/topformflat.l index 2c9962b5f..5a40a0c49 100644 --- a/delta/topformflat.l +++ b/delta/topformflat.l @@ -6,6 +6,9 @@ * very heuristic... */ %{ +#if HAVE_CONFIG_H +# include +#endif #include #include #include // atoi diff --git a/tree-sitter-cpp/CMakeLists.txt b/tree-sitter-cpp/CMakeLists.txt index 7b655fe8f..045bf4b34 100644 --- a/tree-sitter-cpp/CMakeLists.txt +++ b/tree-sitter-cpp/CMakeLists.txt @@ -17,12 +17,14 @@ endif() find_program(TREE_SITTER_CLI tree-sitter DOC "Tree-sitter CLI") -add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" - DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" - COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json - --abi=${TREE_SITTER_ABI_VERSION} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" - COMMENT "Generating parser.c") +if(TREE_SITTER_CLI) + add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c" + DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json" + COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json + --abi=${TREE_SITTER_ABI_VERSION} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + COMMENT "Generating parser.c") +endif() add_library(tree-sitter-cpp src/parser.c) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/scanner.c) diff --git a/tree-sitter/CMakeLists.txt b/tree-sitter/CMakeLists.txt index f6c5bf58d..466b7aa80 100644 --- a/tree-sitter/CMakeLists.txt +++ b/tree-sitter/CMakeLists.txt @@ -9,4 +9,6 @@ target_include_directories(tree-sitter PRIVATE lib/src PUBLIC lib/include ) -target_compile_options(tree-sitter PRIVATE -std=gnu99) +if(NOT MSVC) + target_compile_options(tree-sitter PRIVATE -std=gnu99) +endif()