Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vsenv: fix ValueError parsing multi-line env variables #13682

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 33 additions & 36 deletions mesonbuild/utils/vsenv.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import json
import pathlib
import shutil
import tempfile
import locale

from .. import mlog
from .core import MesonException
Expand All @@ -18,14 +16,6 @@
]


bat_template = '''@ECHO OFF

call "{}"

ECHO {}
SET
'''

# If on Windows and VS is installed but not set up in the environment,
# set it to be runnable. In this way Meson can be directly invoked
# from any shell, VS Code etc.
Expand Down Expand Up @@ -88,34 +78,41 @@ def _setup_vsenv(force: bool) -> bool:
raise MesonException(f'Could not find {bat_path}')

mlog.log('Activating VS', bat_info[0]['catalog']['productDisplayVersion'])
bat_separator = '---SPLIT---'
bat_contents = bat_template.format(bat_path, bat_separator)
bat_file = tempfile.NamedTemporaryFile('w', suffix='.bat', encoding='utf-8', delete=False)
bat_file.write(bat_contents)
bat_file.flush()
bat_file.close()
bat_output = subprocess.check_output(bat_file.name, universal_newlines=True,
encoding=locale.getpreferredencoding(False))
os.unlink(bat_file.name)
bat_lines = bat_output.split('\n')
bat_separator_seen = False
for bat_line in bat_lines:
if bat_line == bat_separator:
bat_separator_seen = True
continue
if not bat_separator_seen:
continue
if not bat_line:
continue
try:
k, v = bat_line.split('=', 1)
except ValueError:
# there is no "=", ignore junk data
pass
else:
os.environ[k] = v

before_separator = '---BEFORE---'
after_separator = '---AFTER---'
# This will print to stdout the env variables set before the VS
# activation and after VS activation so that we can process only
# newly created environment variables. This is required to correctly parse
# environment variables taking into account that some variables
# can have multiple lines. (https://github.com/mesonbuild/meson/pull/13682)
cmd = f'set&& echo {before_separator}&&"{bat_path.absolute()}" && echo {after_separator}&& set'
process = subprocess.Popen(
f'cmd.exe /c "{cmd}"',
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True,
)
stdout, stderr = process.communicate()
if process.returncode != 0:
raise RuntimeError(f"Script failed with error: {stderr.decode()}")
lines = stdout.decode().splitlines()

# Remove the output from the vcvars script
try:
lines_before = lines[:lines.index(before_separator)]
lines_after = lines[lines.index(after_separator) + 1:]
except ValueError:
raise MesonException('Could not find separators in environment variables output')

# Filter out duplicated lines to remove env variables that haven't changed
new_lines = set(lines_before) - set(lines_after)
for line in new_lines:
k, v = line.split('=', 1)
os.environ[k] = v
return True


def setup_vsenv(force: bool = False) -> bool:
try:
return _setup_vsenv(force)
Expand Down
6 changes: 6 additions & 0 deletions unittests/windowstests.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,12 @@ def test_vsenv_option(self):
# Studio is picked, as a regression test for
# https://github.com/mesonbuild/meson/issues/9774
env['PATH'] = get_path_without_cmd('ninja', env['PATH'])
# Add a multiline variable to test that it is handled correctly
# with a line that contains only '=' and a line that would result
# in an invalid variable name.
# see: https://github.com/mesonbuild/meson/pull/13682
env['MULTILINE_VAR_WITH_EQUALS'] = 'Foo\r\n=====\r\n'
env['MULTILINE_VAR_WITH_INVALID_NAME'] = 'Foo\n%=Bar\n'
testdir = os.path.join(self.common_test_dir, '1 trivial')
out = self.init(testdir, extra_args=['--vsenv'], override_envvars=env)
self.assertIn('Activating VS', out)
Expand Down
Loading