Force installation dir options to be inside prefix

With the exception of things like sysconfdir (/etc), every other
installation directory option must be inside the prefix.

Also move the prefix checks to coredata.py since prefix can also be set
from inside project() with default_options and via mesonconf. Earlier
you could set prefix to a relative path that way.

This also allows us to return consistent values for get_option('xxxdir')
regardless of whether relative paths are passed or absolute paths are
passed while setting options on the command-line, via mesonconf, or via
default_options in project(). Now the returned path will *always* be
relative to the prefix.

Includes a unit test for this, and a failing test.

Closes #1299
pull/1324/head
Nirbheek Chauhan 8 years ago committed by Jussi Pakkanen
parent f762e896d2
commit 23f3cec9d0
  1. 55
      mesonbuild/coredata.py
  2. 9
      mesonbuild/mesonmain.py
  3. 26
      run_unittests.py
  4. 2
      test cases/failing/39 libdir must be inside prefix/meson.build
  5. 2
      test cases/failing/40 prefix absolute/meson.build

@ -131,10 +131,54 @@ class CoreData:
# Only to print a warning if it changes between Meson invocations.
self.pkgconf_envvar = os.environ.get('PKG_CONFIG_PATH', '')
def sanitize_prefix(self, prefix):
if not os.path.isabs(prefix):
raise MesonException('prefix value {!r} must be an absolute path'
''.format(prefix))
if prefix.endswith('/') or prefix.endswith('\\'):
# On Windows we need to preserve the trailing slash if the
# string is of type 'C:\' because 'C:' is not an absolute path.
if len(prefix) == 3 and prefix[1] == ':':
pass
else:
prefix = prefix[:-1]
return prefix
def sanitize_dir_option_value(self, prefix, option, value):
'''
If the option is an installation directory option and the value is an
absolute path, check that it resides within prefix and return the value
as a path relative to the prefix.
This way everyone can do f.ex, get_option('libdir') and be sure to get
the library directory relative to prefix.
'''
if option.endswith('dir') and os.path.isabs(value) and \
option not in builtin_dir_noprefix_options:
# Value must be a subdir of the prefix
if os.path.commonpath([value, prefix]) != prefix:
m = 'The value of the {!r} option is {!r} which must be a ' \
'subdir of the prefix {!r}.\nNote that if you pass a ' \
'relative path, it is assumed to be a subdir of prefix.'
raise MesonException(m.format(option, value, prefix))
# Convert path to be relative to prefix
skip = len(prefix) + 1
value = value[skip:]
return value
def init_builtins(self, options):
self.builtins = {}
# Sanitize prefix
options.prefix = self.sanitize_prefix(options.prefix)
# Initialize other builtin options
for key in get_builtin_options():
args = [key] + builtin_options[key][1:-1] + [getattr(options, key, get_builtin_option_default(key))]
if hasattr(options, key):
value = getattr(options, key)
value = self.sanitize_dir_option_value(options.prefix, key, value)
setattr(options, key, value)
else:
value = get_builtin_option_default(key)
args = [key] + builtin_options[key][1:-1] + [value]
self.builtins[key] = builtin_options[key][0](*args)
def get_builtin_option(self, optname):
@ -143,7 +187,11 @@ class CoreData:
raise RuntimeError('Tried to get unknown builtin option %s.' % optname)
def set_builtin_option(self, optname, value):
if optname in self.builtins:
if optname == 'prefix':
value = self.sanitize_prefix(value)
elif optname in self.builtins:
prefix = self.builtins['prefix'].value
value = self.sanitize_dir_option_value(prefix, optname, value)
self.builtins[optname].set_value(value)
else:
raise RuntimeError('Tried to set unknown builtin option %s.' % optname)
@ -235,6 +283,9 @@ builtin_options = {
'errorlogs': [UserBooleanOption, "Whether to print the logs from failing tests.", True],
}
# Installation directories that can reside in a path outside of the prefix
builtin_dir_noprefix_options = {'sysconfdir', 'localstatedir', 'sharedstatedir'}
forbidden_target_names = {'clean': None,
'clean-ctlist': None,
'clean-gcno': None,

@ -73,15 +73,6 @@ class MesonApp:
def __init__(self, dir1, dir2, script_launcher, handshake, options, original_cmd_line_args):
(self.source_dir, self.build_dir) = self.validate_dirs(dir1, dir2, handshake)
if not os.path.isabs(options.prefix):
raise RuntimeError('--prefix value must be an absolute path: {!r}'.format(options.prefix))
if options.prefix.endswith('/') or options.prefix.endswith('\\'):
# On Windows we need to preserve the trailing slash if the
# string is of type 'C:\' because 'C:' is not an absolute path.
if len(options.prefix) == 3 and options.prefix[1] == ':':
pass
else:
options.prefix = options.prefix[:-1]
self.meson_script_launcher = script_launcher
self.options = options
self.original_cmd_line_args = original_cmd_line_args

@ -33,10 +33,11 @@ def get_soname(fname):
return m.group(1)
raise RuntimeError('Could not determine soname:\n\n' + raw_out)
def get_fake_options():
def get_fake_options(prefix):
import argparse
opts = argparse.Namespace()
opts.cross_file = None
opts.prefix = prefix
return opts
class FakeEnvironment(object):
@ -85,7 +86,8 @@ class LinuxlikeTests(unittest.TestCase):
super().tearDown()
def _run(self, command):
self.output += subprocess.check_output(command, env=os.environ.copy())
self.output += subprocess.check_output(command, stderr=subprocess.STDOUT,
env=os.environ.copy())
def init(self, srcdir, extra_args=None):
if extra_args is None:
@ -510,7 +512,7 @@ class LinuxlikeTests(unittest.TestCase):
'''
testdir = os.path.join(self.common_test_dir, '1 trivial')
env = Environment(testdir, self.builddir, self.meson_command,
get_fake_options(), [])
get_fake_options(self.prefix), [])
cc = env.detect_c_compiler(False)
self._test_stds_impl(testdir, cc, 'c')
@ -521,11 +523,10 @@ class LinuxlikeTests(unittest.TestCase):
'''
testdir = os.path.join(self.common_test_dir, '2 cpp')
env = Environment(testdir, self.builddir, self.meson_command,
get_fake_options(), [])
get_fake_options(self.prefix), [])
cpp = env.detect_cpp_compiler(False)
self._test_stds_impl(testdir, cpp, 'cpp')
def test_build_by_default(self):
testdir = os.path.join(self.unit_test_dir, '5 build by default')
self.init(testdir)
@ -537,6 +538,21 @@ class LinuxlikeTests(unittest.TestCase):
self._run(self.ninja_command + ['fooprog'])
self.assertTrue(os.path.exists(exe))
def test_libdir_must_be_inside_prefix(self):
testdir = os.path.join(self.common_test_dir, '1 trivial')
# libdir being inside prefix is ok
args = ['--prefix', '/opt', '--libdir', '/opt/lib32']
self.init(testdir, args)
self.wipe()
# libdir not being inside prefix is not ok
args = ['--prefix', '/usr', '--libdir', '/opt/lib32']
self.assertRaises(subprocess.CalledProcessError, self.init, testdir, args)
self.wipe()
# libdir must be inside prefix even when set via mesonconf
self.init(testdir)
self.assertRaises(subprocess.CalledProcessError, self.setconf, '-Dlibdir=/opt')
class RewriterTests(unittest.TestCase):
def setUp(self):

@ -0,0 +1,2 @@
project('libdir prefix', 'c',
default_options : ['libdir=/opt/lib'])

@ -0,0 +1,2 @@
project('prefix-abs', 'c',
default_options : ['prefix=some/path/notabs'])
Loading…
Cancel
Save