meson: distinguish build and target host binaries

Almost all of the tools we discover during the build process need to be native programs. There are only a handful of exceptions, which typically are programs whose paths we need to embed into the resulting executable so that they can be found on the target system when Git executes. While this distinction typically doesn't matter, it does start to matter when considering cross-compilation where the build and target machines are different. Meson supports cross-compilation via so-called machine files. These machine files allow the user to override parameters for the build machine, but also for the target machine when cross-compiling. Part of the machine file is a section that allows the user to override the location where binaries are to be found in the target system. The following machine file would for example override the path of the POSIX shell: [binaries] sh = '/usr/xpg4/bin/sh' It can be handed over to Meson via `meson setup --cross-file`. We do not handle this correctly right now though because we don't know to distinguish binaries for the build and target hosts at all. Address this by explicitly passing the `native:` parameter to `find_program()`: - When set to `true`, we get binaries discovered on the build host. - When set to `false`, we get either the path specified in the machine file. Or, if no machine file exists or it doesn't specify the binary path, then we fall back to the binary discovered on the build host. As mentioned, only a handful of binaries are not native: only the system shell, Python and Perl need to be treated specially here. Reported-by: Peter Seiderer <ps.report@gmx.net> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Mar 31, 2025 at 10:33 UTC 23633466dfe32f0dac06d89ffd8867d248396111
4 files changed +60 -24
Documentation/meson.build
+6 -6
@@ -207,9 +207,9 @@ manpages = {
207
208 docs_backend = get_option('docs_backend')
209 if docs_backend == 'auto'
210 - if find_program('asciidoc', dirs: program_path, required: false).found()
210 + if find_program('asciidoc', dirs: program_path, native: true, required: false).found()
211 docs_backend = 'asciidoc'
212 - elif find_program('asciidoctor', dirs: program_path, required: false).found()
212 + elif find_program('asciidoctor', dirs: program_path, native: true, required: false).found()
213 docs_backend = 'asciidoctor'
214 else
215 error('Neither asciidoc nor asciidoctor were found.')
@@ -217,7 +217,7 @@ if docs_backend == 'auto'
217 endif
218
219 if docs_backend == 'asciidoc'
220 - asciidoc = find_program('asciidoc', dirs: program_path)
220 + asciidoc = find_program('asciidoc', dirs: program_path, native: true)
221 asciidoc_html = 'xhtml11'
222 asciidoc_docbook = 'docbook'
223 xmlto_extra = [ ]
@@ -246,7 +246,7 @@ if docs_backend == 'asciidoc'
246 asciidoc_conf,
247 ]
248 elif docs_backend == 'asciidoctor'
249 - asciidoctor = find_program('asciidoctor', dirs: program_path)
249 + asciidoctor = find_program('asciidoctor', dirs: program_path, native: true)
250 asciidoc_html = 'xhtml5'
251 asciidoc_docbook = 'docbook5'
252 xmlto_extra = [
@@ -288,7 +288,7 @@ if get_option('breaking_changes')
288 asciidoc_common_options += ['--attribute', 'with-breaking-changes']
289 endif
290
291 -xmlto = find_program('xmlto', dirs: program_path)
291 +xmlto = find_program('xmlto', dirs: program_path, native: true)
292
293 cmd_lists = [
294 'cmds-ancillaryinterrogators.adoc',
@@ -409,7 +409,7 @@ if get_option('docs').contains('html')
409 pointing_to: 'git.html',
410 )
411
412 - xsltproc = find_program('xsltproc', dirs: program_path)
412 + xsltproc = find_program('xsltproc', dirs: program_path, native: true)
413
414 user_manual_xml = custom_target(
415 command: asciidoc_common_options + [
gitweb/meson.build
+1 -1
@@ -1,5 +1,5 @@
1 gitweb_config = configuration_data()
2 -gitweb_config.set_quoted('PERL_PATH', perl.full_path())
2 +gitweb_config.set_quoted('PERL_PATH', target_perl.full_path())
3 gitweb_config.set_quoted('CSSMIN', '')
4 gitweb_config.set_quoted('JSMIN', '')
5 gitweb_config.set_quoted('GIT_BINDIR', get_option('prefix') / get_option('bindir'))
meson.build
+51 -15
@@ -155,6 +155,37 @@
155 # These machine files can be passed to `meson setup` via the `--native-file`
156 # option.
157 #
158 +# Cross compilation
159 +# =================
160 +#
161 +# Machine files can also be used in the context of cross-compilation to
162 +# describe the target machine as well as the cross-compiler toolchain that
163 +# shall be used. An example machine file could look like the following:
164 +#
165 +# [binaries]
166 +# c = 'x86_64-w64-mingw32-gcc'
167 +# cpp = 'x86_64-w64-mingw32-g++'
168 +# ar = 'x86_64-w64-mingw32-ar'
169 +# windres = 'x86_64-w64-mingw32-windres'
170 +# strip = 'x86_64-w64-mingw32-strip'
171 +# exe_wrapper = 'wine64'
172 +# sh = 'C:/Program Files/Git for Windows/usr/bin/sh.exe'
173 +#
174 +# [host_machine]
175 +# system = 'windows'
176 +# cpu_family = 'x86_64'
177 +# cpu = 'x86_64'
178 +# endian = 'little'
179 +#
180 +# These machine files can be passed to `meson setup` via the `--cross-file`
181 +# option.
182 +#
183 +# Note that next to the cross-compiler toolchain, the `[binaries]` section is
184 +# also used to locate a couple of binaries that will be built into Git. This
185 +# includes `sh`, `python` and `perl`, so when cross-compiling Git you likely
186 +# want to set these binary paths in addition to the cross-compiler toolchain
187 +# binaries.
188 +#
189 # Subproject wrappers
190 # ===================
191 #
@@ -173,7 +204,7 @@ project('git', 'c',
204 # The version is only of cosmetic nature, so if we cannot find a shell yet we
205 # simply don't set up a version at all. This may be the case for example on
206 # Windows systems, where we first have to bootstrap the host environment.
176 - version: find_program('sh', required: false).found() ? run_command(
207 + version: find_program('sh', native: true, required: false).found() ? run_command(
208 'GIT-VERSION-GEN', meson.current_source_dir(), '--format=@GIT_VERSION@',
209 capture: true,
210 check: true,
@@ -198,16 +229,18 @@ elif host_machine.system() == 'windows'
229 program_path = [ 'C:/Program Files/Git/bin', 'C:/Program Files/Git/usr/bin' ]
230 endif
231
201 -cygpath = find_program('cygpath', dirs: program_path, required: false)
202 -diff = find_program('diff', dirs: program_path)
203 -git = find_program('git', dirs: program_path, required: false)
204 -sed = find_program('sed', dirs: program_path)
205 -shell = find_program('sh', dirs: program_path)
206 -tar = find_program('tar', dirs: program_path)
232 +cygpath = find_program('cygpath', dirs: program_path, native: true, required: false)
233 +diff = find_program('diff', dirs: program_path, native: true)
234 +git = find_program('git', dirs: program_path, native: true, required: false)
235 +sed = find_program('sed', dirs: program_path, native: true)
236 +shell = find_program('sh', dirs: program_path, native: true)
237 +tar = find_program('tar', dirs: program_path, native: true)
238 +
239 +target_shell = find_program('sh', dirs: program_path, native: false)
240
241 # Sanity-check that programs required for the build exist.
242 foreach tool : ['cat', 'cut', 'grep', 'sort', 'tr', 'uname']
210 - find_program(tool, dirs: program_path)
243 + find_program(tool, dirs: program_path, native: true)
244 endforeach
245
246 script_environment = environment()
@@ -706,7 +739,7 @@ libgit_c_args = [
739 '-DGIT_LOCALE_PATH="' + get_option('localedir') + '"',
740 '-DGIT_MAN_PATH="' + get_option('mandir') + '"',
741 '-DPAGER_ENV="' + get_option('pager_environment') + '"',
709 - '-DSHELL_PATH="' + fs.as_posix(shell.full_path()) + '"',
742 + '-DSHELL_PATH="' + fs.as_posix(target_shell.full_path()) + '"',
743 ]
744 libgit_include_directories = [ '.' ]
745 libgit_dependencies = [ ]
@@ -761,6 +794,7 @@ endif
794 build_options_config.set_quoted('X', executable_suffix)
795
796 python = import('python').find_installation('python3', required: get_option('python'))
797 +target_python = find_program('python3', native: false, required: python.found())
798 if python.found()
799 build_options_config.set('NO_PYTHON', '')
800 else
@@ -790,9 +824,11 @@ endif
824 # which we can do starting with Meson 1.5.0 and newer, or we have to
825 # match against the minor version.
826 if meson.version().version_compare('>=1.5.0')
793 - perl = find_program('perl', dirs: program_path, required: perl_required, version: '>=5.26.0', version_argument: '-V:version')
827 + perl = find_program('perl', dirs: program_path, native: true, required: perl_required, version: '>=5.26.0', version_argument: '-V:version')
828 + target_perl = find_program('perl', dirs: program_path, native: false, required: perl.found(), version: '>=5.26.0', version_argument: '-V:version')
829 else
795 - perl = find_program('perl', dirs: program_path, required: perl_required, version: '>=26')
830 + perl = find_program('perl', dirs: program_path, native: true, required: perl_required, version: '>=26')
831 + target_perl = find_program('perl', dirs: program_path, native: false, required: perl.found(), version: '>=26')
832 endif
833 perl_features_enabled = perl.found() and get_option('perl').allowed()
834 if perl_features_enabled
@@ -843,7 +879,7 @@ else
879 build_options_config.set('NO_PTHREADS', '1')
880 endif
881
846 -msgfmt = find_program('msgfmt', dirs: program_path, required: false)
882 +msgfmt = find_program('msgfmt', dirs: program_path, native: true, required: false)
883 gettext_option = get_option('gettext').disable_auto_if(not msgfmt.found())
884 if not msgfmt.found() and gettext_option.enabled()
885 error('Internationalization via libintl requires msgfmt')
@@ -1974,9 +2010,9 @@ foreach key, value : {
2010 'GIT_TEST_TEMPLATE_DIR': meson.project_build_root() / 'templates',
2011 'GIT_TEST_TEXTDOMAINDIR': meson.project_build_root() / 'po',
2012 'PAGER_ENV': get_option('pager_environment'),
1977 - 'PERL_PATH': perl.found() ? perl.full_path() : '',
1978 - 'PYTHON_PATH': python.found () ? python.full_path() : '',
1979 - 'SHELL_PATH': shell.full_path(),
2013 + 'PERL_PATH': target_perl.found() ? target_perl.full_path() : '',
2014 + 'PYTHON_PATH': target_python.found () ? target_python.full_path() : '',
2015 + 'SHELL_PATH': target_shell.full_path(),
2016 'TAR': tar.full_path(),
2017 'TEST_OUTPUT_DIRECTORY': test_output_directory,
2018 'TEST_SHELL_PATH': shell.full_path(),
templates/meson.build
+2 -2
@@ -1,6 +1,6 @@
1 template_config = configuration_data()
2 -template_config.set('PERL_PATH', perl.found() ? fs.as_posix(perl.full_path()) : '')
3 -template_config.set('SHELL_PATH', fs.as_posix(shell.full_path()))
2 +template_config.set('PERL_PATH', target_perl.found() ? fs.as_posix(target_perl.full_path()) : '')
3 +template_config.set('SHELL_PATH', fs.as_posix(target_shell.full_path()))
4 template_config.set('GITWEBDIR', fs.as_posix(get_option('prefix') / get_option('datadir') / 'gitweb'))
5
6 configure_file(