diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 900be9957a23fc..7f55f8b3a91d6c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -248,6 +248,58 @@ jobs: with: name: failed-tests-windows-vs-${{ matrix.nr }} path: ${{env.FAILED_TEST_ARTIFACTS}} + + windows-meson-build: + name: win+Meson build + needs: ci-config + if: needs.ci-config.outputs.enabled == 'yes' + runs-on: windows-latest + concurrency: + group: windows-meson-build-${{ github.ref }} + cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - name: Set up dependencies + shell: pwsh + run: pip install meson ninja + - name: Setup + shell: pwsh + run: meson setup build -Dperl=disabled + - name: Compile + shell: pwsh + run: meson compile -C build + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: windows-meson-artifacts + path: build + windows-meson-test: + name: win+Meson test + runs-on: windows-latest + needs: [ci-config, windows-meson-build] + strategy: + fail-fast: false + matrix: + nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + concurrency: + group: windows-meson-test-${{ matrix.nr }}-${{ github.ref }} + cancel-in-progress: ${{ needs.ci-config.outputs.skip_concurrent == 'yes' }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - name: Set up dependencies + shell: pwsh + run: pip install meson ninja + - name: Download build artifacts + uses: actions/download-artifact@v4 + with: + name: windows-meson-artifacts + path: build + - name: Test + shell: pwsh + run: meson test -C build --list | Select-Object -Skip 1 | Select-String .* | Group-Object -Property { $_.LineNumber % 10 } | Where-Object Name -EQ ${{ matrix.nr }} | ForEach-Object { meson test -C build --no-rebuild --print-errorlogs $_.Group } + regular: name: ${{matrix.vector.jobname}} (${{matrix.vector.pool}}) needs: ci-config diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9254e01583306e..4976e18a050329 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -149,6 +149,44 @@ test:mingw64: - git-sdk/usr/bin/bash.exe -l -c 'ci/print-test-failures.sh' parallel: 10 +.msvc-meson: + tags: + - saas-windows-medium-amd64 + before_script: + - choco install -y git meson ninja openssl + - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 + - refreshenv + # The certificate store for Python on Windows is broken and fails to fetch + # certificates, see https://bugs.python.org/issue36011. This seems to + # mostly be an issue with how the GitLab image is set up as it is a + # non-issue on GitHub Actions. Work around the issue by importing + # cetrificates manually. + - Invoke-WebRequest https://curl.haxx.se/ca/cacert.pem -OutFile cacert.pem + - openssl pkcs12 -export -nokeys -in cacert.pem -out certs.pfx -passout "pass:" + - Import-PfxCertificate -CertStoreLocation Cert:\LocalMachine\Root -FilePath certs.pfx + +build:msvc-meson: + extends: .msvc-meson + stage: build + script: + - meson setup build -Dperl=disabled + - meson compile -C build + artifacts: + paths: + - build + +test:msvc-meson: + extends: .msvc-meson + stage: test + when: manual + timeout: 6h + needs: + - job: "build:msvc-meson" + artifacts: true + script: + - meson test -C build --list | Select-Object -Skip 1 | Select-String .* | Group-Object -Property { $_.LineNumber % $Env:CI_NODE_TOTAL + 1 } | Where-Object Name -EQ $Env:CI_NODE_INDEX | ForEach-Object { meson test -C build --no-rebuild --print-errorlogs $_.Group } + parallel: 10 + test:fuzz-smoke-tests: image: ubuntu:latest stage: test diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index f2af817feaeb38..f6764555ce6fd4 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -5,20 +5,32 @@ DEF_VER=v2.48.0 LF=' ' -if test "$#" -ne 3 +if test "$#" -lt 2 || test "$#" -gt 3 then - echo >&2 "USAGE: $0 " + echo >&2 "USAGE: $0 (--format=|) []" exit 1 fi SOURCE_DIR="$1" -INPUT="$2" -OUTPUT="$3" -if ! test -f "$INPUT" +case "$2" in +--format=*) + INPUT="${2#--format=}" + ;; +*) + if ! test -f "$2" + then + echo >&2 "Input is not a file: $2" + exit 1 + fi + INPUT=$(cat "$2") + ;; +esac + +OUTPUT="$3" +if test -n "$OUTPUT" && test -n "$MESON_DIST_ROOT" then - echo >&2 "Input is not a file: $INPUT" - exit 1 + OUTPUT="$MESON_DIST_ROOT/$OUTPUT" fi # Protect us from reading Git version information outside of the Git directory @@ -39,13 +51,9 @@ then test -d "${GIT_DIR:-.git}" || test -f "$SOURCE_DIR"/.git; } && - VN=$(git -C "$SOURCE_DIR" describe --match "v[0-9]*" HEAD 2>/dev/null) && + VN=$(git -C "$SOURCE_DIR" describe --dirty --match="v[0-9]*" 2>/dev/null) && case "$VN" in *$LF*) (exit 1) ;; - v[0-9]*) - git -C "$SOURCE_DIR" update-index -q --refresh - test -z "$(git -C "$SOURCE_DIR" diff-index --name-only HEAD --)" || - VN="$VN-dirty" ;; esac then VN=$(echo "$VN" | sed -e 's/-/./g'); @@ -78,19 +86,25 @@ read GIT_MAJOR_VERSION GIT_MINOR_VERSION GIT_MICRO_VERSION GIT_PATCH_LEVEL trail $(echo "$GIT_VERSION" 0 0 0 0 | tr '.a-zA-Z-' ' ') EOF -sed -e "s|@GIT_VERSION@|$GIT_VERSION|" \ +REPLACED=$(printf "%s" "$INPUT" | sed -e "s|@GIT_VERSION@|$GIT_VERSION|" \ -e "s|@GIT_MAJOR_VERSION@|$GIT_MAJOR_VERSION|" \ -e "s|@GIT_MINOR_VERSION@|$GIT_MINOR_VERSION|" \ -e "s|@GIT_MICRO_VERSION@|$GIT_MICRO_VERSION|" \ -e "s|@GIT_PATCH_LEVEL@|$GIT_PATCH_LEVEL|" \ -e "s|@GIT_BUILT_FROM_COMMIT@|$GIT_BUILT_FROM_COMMIT|" \ -e "s|@GIT_USER_AGENT@|$GIT_USER_AGENT|" \ - -e "s|@GIT_DATE@|$GIT_DATE|" \ - "$INPUT" >"$OUTPUT".$$+ + -e "s|@GIT_DATE@|$GIT_DATE|" +) -if ! test -f "$OUTPUT" || ! cmp "$OUTPUT".$$+ "$OUTPUT" >/dev/null +if test -z "$OUTPUT" then - mv "$OUTPUT".$$+ "$OUTPUT" + printf "%s\n" "$REPLACED" else - rm "$OUTPUT".$$+ + printf "%s\n" "$REPLACED" >"$OUTPUT".$$+ + if ! test -f "$OUTPUT" || ! cmp "$OUTPUT".$$+ "$OUTPUT" >/dev/null + then + mv "$OUTPUT".$$+ "$OUTPUT" + else + rm "$OUTPUT".$$+ + fi fi diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 76667a1277720d..964322055f5db0 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -52,8 +52,10 @@ esac case "$jobname" in *-meson) group "Configure" meson setup build . \ + --fatal-meson-warnings \ --warnlevel 2 --werror \ - --wrap-mode nofallback + --wrap-mode nofallback \ + -Dfuzzers=true group "Build" meson compile -C build -- if test -n "$run_tests" then diff --git a/meson.build b/meson.build index 0064eb64f546a6..30d7a894904dfe 100644 --- a/meson.build +++ b/meson.build @@ -170,7 +170,22 @@ project('git', 'c', meson_version: '>=0.61.0', - version: 'v2.47.GIT', + # The version is only of cosmetic nature, so if we cannot find a shell yet we + # simply don't set up a version at all. This may be the case for example on + # Windows systems, where we first have to bootstrap the host environment. + version: find_program('sh', required: false).found() ? run_command( + 'GIT-VERSION-GEN', meson.current_source_dir(), '--format=@GIT_VERSION@', + capture: true, + check: true, + ).stdout().strip() : 'unknown', + default_options: [ + # Git requires C99 with GNU extensions, which of course isn't supported by + # MSVC. Funny enough, C99 doesn't work with MSVC either, as it has only + # learned to define __STDC_VERSION__ with C11 and later. We thus require + # GNU C99 and fall back to C11. Meson only learned to handle the fallback + # with version 1.3.0, so on older versions we use GNU C99 unconditionally. + 'c_std=' + (meson.version().version_compare('>=1.3.0') ? 'gnu99,c11' : 'gnu99'), + ], ) fs = import('fs') @@ -480,6 +495,13 @@ libgit_sources = [ 'xdiff/xutils.c', ] +libgit_sources += custom_target( + input: 'command-list.txt', + output: 'command-list.h', + command: [shell, meson.current_source_dir() + '/generate-cmdlist.sh', meson.current_source_dir(), '@OUTPUT@'], + env: script_environment, +) + builtin_sources = [ 'builtin/add.c', 'builtin/am.c', @@ -607,14 +629,7 @@ builtin_sources = [ 'builtin/write-tree.c', ] -libgit_sources += custom_target( - input: 'command-list.txt', - output: 'command-list.h', - command: [shell, meson.current_source_dir() + '/generate-cmdlist.sh', meson.current_source_dir(), '@OUTPUT@'], - env: script_environment, -) - -libgit_sources += custom_target( +builtin_sources += custom_target( output: 'config-list.h', command: [ shell, @@ -625,7 +640,7 @@ libgit_sources += custom_target( env: script_environment, ) -libgit_sources += custom_target( +builtin_sources += custom_target( input: 'Documentation/githooks.txt', output: 'hook-list.h', command: [ @@ -1325,15 +1340,19 @@ if not meson.is_cross_build() and fs.exists('/dev/tty') libgit_c_args += '-DHAVE_DEV_TTY' endif +csprng_backend = get_option('csprng_backend') https_backend = get_option('https_backend') +sha1_backend = get_option('sha1_backend') +sha1_unsafe_backend = get_option('sha1_unsafe_backend') +sha256_backend = get_option('sha256_backend') -security_framework = dependency('Security', required: https_backend == 'CommonCrypto') +security_framework = dependency('Security', required: 'CommonCrypto' in [https_backend, sha1_backend, sha1_unsafe_backend]) core_foundation_framework = dependency('CoreFoundation', required: security_framework.found()) if https_backend == 'auto' and security_framework.found() https_backend = 'CommonCrypto' endif -openssl_required = https_backend == 'openssl' or get_option('sha1_backend') == 'openssl' or get_option('sha256_backend') == 'openssl' +openssl_required = 'openssl' in [csprng_backend, https_backend, sha1_backend, sha1_unsafe_backend, sha256_backend] openssl = dependency('openssl', required: openssl_required, default_options: ['default_library=static']) if https_backend == 'auto' and openssl.found() https_backend = 'openssl' @@ -1354,7 +1373,6 @@ if https_backend != 'openssl' libgit_c_args += '-DNO_OPENSSL' endif -sha1_backend = get_option('sha1_backend') if sha1_backend == 'sha1dc' libgit_c_args += '-DSHA1_DC' libgit_c_args += '-DSHA1DC_NO_STANDARD_INCLUDES=1' @@ -1367,22 +1385,40 @@ if sha1_backend == 'sha1dc' 'sha1dc/sha1.c', 'sha1dc/ubc_check.c', ] -elif sha1_backend == 'common-crypto' +endif +if sha1_backend == 'CommonCrypto' or sha1_unsafe_backend == 'CommonCrypto' + if sha1_backend == 'CommonCrypto' + libgit_c_args += '-DSHA1_APPLE' + endif + if sha1_unsafe_backend == 'CommonCrypto' + libgit_c_args += '-DSHA1_APPLE_UNSAFE' + endif + libgit_c_args += '-DCOMMON_DIGEST_FOR_OPENSSL' - libgit_c_args += '-DSHA1_APPLE' # Apple CommonCrypto requires chunking libgit_c_args += '-DSHA1_MAX_BLOCK_SIZE=1024L*1024L*1024L' -elif sha1_backend == 'openssl' - libgit_c_args += '-DSHA1_OPENSSL' +endif +if sha1_backend == 'openssl' or sha1_unsafe_backend == 'openssl' + if sha1_backend == 'openssl' + libgit_c_args += '-DSHA1_OPENSSL' + endif + if sha1_unsafe_backend == 'openssl' + libgit_c_args += '-DSHA1_OPENSSL_UNSAFE' + endif + libgit_dependencies += openssl -elif sha1_backend == 'block' - libgit_c_args += '-DSHA1_BLK' +endif +if sha1_backend == 'block' or sha1_unsafe_backend == 'block' + if sha1_backend == 'block' + libgit_c_args += '-DSHA1_BLK' + endif + if sha1_unsafe_backend == 'block' + libgit_c_args += '-DSHA1_BLK_UNSAFE' + endif + libgit_sources += 'block-sha1/sha1.c' -else - error('Unhandled SHA1 backend ' + sha1_backend) endif -sha256_backend = get_option('sha256_backend') if sha256_backend == 'openssl' libgit_c_args += '-DSHA256_OPENSSL' libgit_dependencies += openssl @@ -1401,18 +1437,30 @@ else error('Unhandled SHA256 backend ' + sha256_backend) endif -if compiler.has_header_symbol('stdlib.h', 'arc4random_buf') +# Backends are ordered to reflect our preference for more secure and faster +# ones over the ones that are less so. +if csprng_backend in ['auto', 'arc4random'] and compiler.has_header_symbol('stdlib.h', 'arc4random_buf', required: csprng_backend == 'arc4random') libgit_c_args += '-DHAVE_ARC4RANDOM' -elif compiler.has_header_symbol('bsd/stdlib.h', 'arc4random_buf') + csprng_backend = 'arc4random' +elif csprng_backend in ['auto', 'arc4random_bsd'] and compiler.has_header_symbol('bsd/stdlib.h', 'arc4random_buf', required: csprng_backend == 'arc4random_bsd') libgit_c_args += '-DHAVE_ARC4RANDOM_BSD' -elif compiler.has_function('getrandom', prefix: '#include ') + csprng_backend = 'arc4random_bsd' +elif csprng_backend in ['auto', 'getrandom'] and compiler.has_header_symbol('sys/random.h', 'getrandom', required: csprng_backend == 'getrandom') libgit_c_args += '-DHAVE_GETRANDOM' -elif compiler.has_function('getentropy', prefix: '#include ') + csprng_backend = 'getrandom' +elif csprng_backend in ['auto', 'getentropy'] and compiler.has_header_symbol('unistd.h', 'getentropy', required: csprng_backend == 'getentropy') libgit_c_args += '-DHAVE_GETENTROPY' -elif compiler.has_function('RtlGenRandom', prefix: '#include \n#include ') + csprng_backend = 'getentropy' +elif csprng_backend in ['auto', 'rtlgenrandom'] and compiler.has_header_symbol('ntsecapi.h', 'RtlGenRandom', prefix: '#include ', required: csprng_backend == 'rtlgenrandom') libgit_c_args += '-DHAVE_RTLGENRANDOM' -elif openssl.found() + csprng_backend = 'rtlgenrandom' +elif csprng_backend in ['auto', 'openssl'] and openssl.found() libgit_c_args += '-DHAVE_OPENSSL_CSPRNG' + csprng_backend = 'openssl' +elif csprng_backend in ['auto', 'urandom'] + csprng_backend = 'urandom' +else + error('Unsupported CSPRNG backend: ' + csprng_backend) endif if get_option('runtime_prefix') @@ -1879,6 +1927,10 @@ if get_option('tests') subdir('t') endif +if get_option('fuzzers') + subdir('oss-fuzz') +endif + subdir('bin-wrappers') if get_option('docs') != [] subdir('Documentation') @@ -1912,6 +1964,26 @@ configure_file( configuration: build_options_config, ) +# Development environments can be used via `meson devenv -C `. This +# allows you to execute test scripts directly with the built Git version and +# puts the built version of Git in your PATH. +devenv = environment() +devenv.set('GIT_BUILD_DIR', meson.current_build_dir()) +devenv.prepend('PATH', meson.current_build_dir() / 'bin-wrappers') +meson.add_devenv(devenv) + +# Generate the 'version' file in the distribution tarball. This is used via +# `meson dist -C ` to populate the source archive with the Git +# version that the archive is being generated from. GIT-VERSION-GEN knows to +# pick up this file. +meson.add_dist_script( + shell, + meson.current_source_dir() / 'GIT-VERSION-GEN', + meson.current_source_dir(), + '--format=@GIT_VERSION@', + 'version', +) + summary({ 'curl': curl.found(), 'expat': expat.found(), @@ -1923,3 +1995,11 @@ summary({ 'perl': perl_features_enabled, 'python': python.found(), }, section: 'Auto-detected features') + +summary({ + 'csprng': csprng_backend, + 'https': https_backend, + 'sha1': sha1_backend, + 'sha1_unsafe': sha1_unsafe_backend, + 'sha256': sha256_backend, +}, section: 'Backends') diff --git a/meson_options.txt b/meson_options.txt index f50bb40cdf6046..5429022f306211 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -47,12 +47,16 @@ option('regex', type: 'feature', value: 'auto', description: 'Use the system-provided regex library instead of the bundled one.') # Backends. +option('csprng_backend', type: 'combo', value: 'auto', choices: ['auto', 'arc4random', 'arc4random_bsd', 'getrandom', 'getentropy', 'rtlgenrandom', 'openssl', 'urandom'], + description: 'The backend to use for generating cryptographically-secure pseudo-random numbers.') option('https_backend', type: 'combo', value: 'auto', choices: ['auto', 'openssl', 'CommonCrypto', 'none'], description: 'The HTTPS backend to use when connecting to remotes.') -option('sha1_backend', type: 'combo', choices: ['openssl', 'block', 'sha1dc', 'common-crypto'], value: 'sha1dc', - description: 'The backend used for hashing objects with the SHA1 object format') +option('sha1_backend', type: 'combo', choices: ['openssl', 'block', 'sha1dc', 'CommonCrypto'], value: 'sha1dc', + description: 'The backend used for hashing objects with the SHA1 object format.') +option('sha1_unsafe_backend', type: 'combo', choices: ['openssl', 'block', 'CommonCrypto', 'none'], value: 'none', + description: 'The backend used for hashing data with the SHA1 object format in case no cryptographic security is needed.') option('sha256_backend', type: 'combo', choices: ['openssl', 'nettle', 'gcrypt', 'block'], value: 'block', - description: 'The backend used for hashing objects with the SHA256 object format') + description: 'The backend used for hashing objects with the SHA256 object format.') # Build tweaks. option('macos_use_homebrew_gettext', type: 'boolean', value: true, @@ -93,3 +97,5 @@ option('tests', type: 'boolean', value: true, description: 'Enable building tests. This requires Perl, but is separate from the "perl" option such that you can build tests without Perl features enabled.') option('test_output_directory', type: 'string', description: 'Path to the directory used to store test outputs') +option('fuzzers', type: 'boolean', value: false, + description: 'Enable building fuzzers.') diff --git a/oss-fuzz/meson.build b/oss-fuzz/meson.build new file mode 100644 index 00000000000000..ed79665501655e --- /dev/null +++ b/oss-fuzz/meson.build @@ -0,0 +1,20 @@ +fuzz_programs = [ + 'fuzz-commit-graph.c', + 'fuzz-config.c', + 'fuzz-credential-from-url-gently.c', + 'fuzz-date.c', + 'fuzz-pack-headers.c', + 'fuzz-pack-idx.c', + 'fuzz-parse-attr-line.c', + 'fuzz-url-decode-mem.c', +] + +foreach fuzz_program : fuzz_programs + executable(fs.stem(fuzz_program), + sources: [ + 'dummy-cmd-main.c', + fuzz_program, + ], + dependencies: [libgit, common_main], + ) +endforeach