mesonbuild / meson

The Meson Build System
http://mesonbuild.com
Apache License 2.0
5.6k stars 1.63k forks source link

Meson doesn't correctly run shebang scripts coming from file(), configure_file(), or custom_target() #11255

Open LunarLambda opened 1 year ago

LunarLambda commented 1 year ago

To reproduce, on Meson 1.0.0, Ninja 1.11.1, Python 3.10, on a Linux machine, although similar behaviour happens on Windows:

meson.build:

project('meson-11255')

hi_str = 'hi.py'
hi_file = files('hi.py')
hi_prog = find_program('hi.py')
hi_cfg = configure_file(input: 'hi.py', output: 'hi_cfg.py', copy: true)
hi_tgt = custom_target('hi', input: 'hi.py', output: 'hi_tgt.py', command: ['copy.py', '@INPUT@', '@OUTPUT@'])

write_str = 'write.py'
write_file = files('write.py')
write_prog = find_program('write.py')
write_cfg = configure_file(input: 'write.py', output: 'write_cfg.py', copy: true)
write_tgt = custom_target('write', input: 'write.py', output: 'write_tgt.py', command: ['copy.py', '@INPUT@', '@OUTPUT@'])

#
# Workaround: find_program can 'launder' file objects:
#

# OK
hi_prgf = find_program(hi_file)

# OK
hi_prgc = find_program(hi_cfg)

#
# run_command()
#

# OK
run_command(hi_str, check: true)
# OK
run_command(hi_file, check: true)
# OK
run_command(hi_prog, check: true)
# OK
run_command(hi_cfg, check: true)

#
# run_target()
#

# OK
run_target('run-str', command: [hi_str])
# FAIL: Permission denied (command: env with absolute path)
run_target('run-file', command: [hi_file])
# OK
run_target('run-prog', command: [hi_prog])
# FAIL: File not found (command: env with relative path)
run_target('run-cfg', command: [hi_cfg])
# FAIL: Permission denied (command: env with absolute path)
run_target('run-tgt', command: [hi_tgt])

#
# custom_target()
#

# OK
custom_target('write-str', output: 'write-str', command: [write_str, '@OUTPUT@'])

# FAIL: Permission denied (command: unmodified absolute path to script)
custom_target('write-file', output: 'write-file', command: [write_file, '@OUTPUT@'])

# OK
custom_target('write-prog', output: 'write-prog', command: [write_prog, '@OUTPUT@'])

# FAIL: File not found (command: unmodified relative path to script)
custom_target('write-cfg', output: 'write-cfg', command: [write_cfg, '@OUTPUT@'])

# FAIL: Permission denied (command: unmodified absolute path to script)
custom_target('write-tgt', output: 'write-tgt', command: [write_tgt, '@OUTPUT@'])

#
# custom_target(capture: true)
#

# OK
custom_target('tgt-str', output: 'tgt-str', capture: true, command: [hi_str])

# FAIL: Permission denied (command: meson internal with absolute path)
custom_target('tgt-file', output: 'tgt-file', capture: true, command: [hi_file])

# OK
custom_target('tgt-prog', output: 'tgt-prog', capture: true, command: [hi_prog])

# FAIL: File not found (command: meson internal with relative path)
custom_target('tgt-cfg', output: 'tgt-cfg', capture: true, command: [hi_cfg])

# FAIL: Permission denied (command: meson internal with absolute path)
custom_target('tgt-tgt', output: 'tgt-tgt', capture: true, command: [hi_tgt])

hi.py:

#!/usr/bin/env python3
print('Hello')

write.py:

#!/usr/bin/env python3
import sys
print('Hello', file=open(sys.argv[1], 'w+'))

copy.py:

#!/usr/bin/env python3
import shutil
from sys import argv

shutil.copy(argv[1], argv[2])

It's important that hi.py and write.py do not have the executable bit set (unix), although in the case of run-cfg and write-cfg, it failed even with it set (!!!)

I didn't test generator() because that's a bit more involved, but since it spits out files I assume it behaves similarly to the configure_file() case, possibly with the same issue as above.

Open for original issue text
## See comment below for repro My project has a python script. shebang `#!/usr/bin/env python3`, which per https://github.com/mesonbuild/meson/discussions/10978#discussioncomment-4029863 should correctly work even if the executable bit is missing. I just rewrote a wrap file to use wrap-file instead of wrap-git, but the source archive is a zip and lacks the ability to store the execute bit, which breaks the build. ``` [371/379] Generating examples/template/template_rom with a custom command FAILED: examples/template/template.gba /home/lambda/dev/gba/meson/b/subprojects/sdk-seven-0.8.5/tools/makerom examples/template/template.elf examples/template/template.gba /bin/sh: line 1: /home/lambda/dev/gba/meson/b/subprojects/sdk-seven-0.8.5/tools/makerom: Permission denied ``` makerom is generated from another python script, as a `custom_target`. Then, makerom is passed as the command to run for another `custom_target`, template.gba. Looking at build.ninja reveals: 1. makerom build rule uses `/usr/bin/python /path/to/the/project/copy` 2. template.gba build rule uses `/path/to/the/project/build/makerom` without python (even though both scripts have the same shebang) What seems to be happening is that when the output of a `custom_target` is passed as a command, it is always run as a "plain executable", meaning the script detection logic is skipped.

version #s: Meson 1.0.0, Ninja 1.11.1, Python 3.10.9, Cross build, on Arch Linux

LunarLambda commented 1 year ago

https://github.com/mesonbuild/meson/blob/master/mesonbuild/programs.py#L179

After checking through the code @eli-schwartz linked in the discussion I mentioned, I noticed it's part of ExternalProgram, which for command execution, counts the return values of get_compiler(), find_program(), as well as raw file paths, but not the outputs of build targets, like executable(), or, as I expect, custom_target().

I will try to reproduce it based on this guess

LunarLambda commented 9 months ago

This behaviour still happens identically as of Meson 1.3.1.