Add support for custom dist scripts.

pull/4092/head
Jussi Pakkanen 6 years ago
parent 1ffc8de5e8
commit fb770e1e3d
  1. 9
      docs/markdown/Reference-manual.md
  2. 2
      docs/markdown/Reference-tables.md
  3. 12
      docs/markdown/snippets/distscript.md
  4. 1
      mesonbuild/build.py
  5. 10
      mesonbuild/interpreter.py
  6. 29
      mesonbuild/scripts/dist.py
  7. 50
      run_unittests.py
  8. 7
      test cases/unit/35 dist script/meson.build
  9. 7
      test cases/unit/35 dist script/prog.c
  10. 12
      test cases/unit/35 dist script/replacer.py

@ -1416,6 +1416,15 @@ The `meson` object allows you to introspect various properties of the
system. This object is always mapped in the `meson` variable. It has system. This object is always mapped in the `meson` variable. It has
the following methods. the following methods.
- `add_dist_script` causes the script given as argument to run during
`dist` operation after the distribution source has been generated
but before it is archived. Note that this runs the script file that
is in the _staging_ directory, not the one in the source
directory. If the script file can not be found in the staging
directory, it is a hard error. This command can only invoked from
the main project, calling it from a subproject is a hard
error. Available since 0.48.0.
- `add_install_script(script_name, arg1, arg2, ...)` causes the script - `add_install_script(script_name, arg1, arg2, ...)` causes the script
given as an argument to be run during the install step, this script given as an argument to be run during the install step, this script
will have the environment variables `MESON_SOURCE_ROOT`, will have the environment variables `MESON_SOURCE_ROOT`,

@ -33,6 +33,8 @@ These are return values of the `get_id` method in a compiler object.
| MESON_BUILD_ROOT | Absolute path to the build dir | | MESON_BUILD_ROOT | Absolute path to the build dir |
| MESONINTROSPECT | Command to run to run the introspection command, may be of the form `python /path/to/meson introspect`, user is responsible for splitting the path if necessary. | | MESONINTROSPECT | Command to run to run the introspection command, may be of the form `python /path/to/meson introspect`, user is responsible for splitting the path if necessary. |
| MESON_SUBDIR | Current subdirectory, only set for `run_command` | | MESON_SUBDIR | Current subdirectory, only set for `run_command` |
| MESON_DIST_ROOT | Points to the root of the staging directory, only set when running `dist` scripts |
## CPU families ## CPU families

@ -0,0 +1,12 @@
## Dist scripts
You can now specify scripts that are run as part of the `dist`
target. An example usage would go like this:
```meson
project('foo', 'c')
# other stuff here
meson.add_dist_script('dist_cleanup.py')
```

@ -123,6 +123,7 @@ class Build:
self.subproject_dir = '' self.subproject_dir = ''
self.install_scripts = [] self.install_scripts = []
self.postconf_scripts = [] self.postconf_scripts = []
self.dist_scripts = []
self.install_dirs = [] self.install_dirs = []
self.dep_manifest_name = None self.dep_manifest_name = None
self.dep_manifest = {} self.dep_manifest = {}

@ -1619,6 +1619,7 @@ class MesonMain(InterpreterObject):
'build_root': self.build_root_method, 'build_root': self.build_root_method,
'add_install_script': self.add_install_script_method, 'add_install_script': self.add_install_script_method,
'add_postconf_script': self.add_postconf_script_method, 'add_postconf_script': self.add_postconf_script_method,
'add_dist_script': self.add_dist_script_method,
'install_dependency_manifest': self.install_dependency_manifest_method, 'install_dependency_manifest': self.install_dependency_manifest_method,
'override_find_program': self.override_find_program_method, 'override_find_program': self.override_find_program_method,
'project_version': self.project_version_method, 'project_version': self.project_version_method,
@ -1661,6 +1662,15 @@ class MesonMain(InterpreterObject):
script = self._find_source_script(args[0], args[1:]) script = self._find_source_script(args[0], args[1:])
self.build.postconf_scripts.append(script) self.build.postconf_scripts.append(script)
@permittedKwargs({})
def add_dist_script_method(self, args, kwargs):
if len(args) != 1:
raise InterpreterException('add_dist_script takes exactly one argument')
check_stringlist(args, 'add_dist_script argument must be a string')
if self.interpreter.subproject != '':
raise InterpreterException('add_dist_script may not be used in a subproject.')
self.build.dist_scripts.append(os.path.join(self.interpreter.subdir, args[0]))
@noPosargs @noPosargs
@permittedKwargs({}) @permittedKwargs({})
def current_source_dir_method(self, args, kwargs): def current_source_dir_method(self, args, kwargs):

@ -15,6 +15,7 @@
import lzma import lzma
import os import os
import sys
import shutil import shutil
import subprocess import subprocess
import pickle import pickle
@ -23,6 +24,7 @@ import tarfile, zipfile
import tempfile import tempfile
from glob import glob from glob import glob
from mesonbuild.environment import detect_ninja from mesonbuild.environment import detect_ninja
from mesonbuild.dependencies import ExternalProgram
from mesonbuild.mesonlib import windows_proof_rmtree from mesonbuild.mesonlib import windows_proof_rmtree
def create_hash(fname): def create_hash(fname):
@ -73,7 +75,23 @@ def process_submodules(dirname):
del_gitfiles(os.path.join(dirname, v)) del_gitfiles(os.path.join(dirname, v))
def create_dist_git(dist_name, src_root, bld_root, dist_sub): def run_dist_scripts(dist_root, dist_scripts):
assert(os.path.isabs(dist_root))
env = os.environ.copy()
env['MESON_DIST_ROOT'] = dist_root
for d in dist_scripts:
print('Processing dist script %s.' % d)
ddir, dname = os.path.split(d)
ep = ExternalProgram(dname,
search_dir=os.path.join(dist_root, ddir),
silent=True)
if not ep.found():
sys.exit('Script %s could not be found in dist directory.' % d)
pc = subprocess.run(ep.command, env=env)
if pc.returncode != 0:
sys.exit('Dist script errored out.')
def create_dist_git(dist_name, src_root, bld_root, dist_sub, dist_scripts):
distdir = os.path.join(dist_sub, dist_name) distdir = os.path.join(dist_sub, dist_name)
if os.path.exists(distdir): if os.path.exists(distdir):
shutil.rmtree(distdir) shutil.rmtree(distdir)
@ -81,6 +99,7 @@ def create_dist_git(dist_name, src_root, bld_root, dist_sub):
subprocess.check_call(['git', 'clone', '--shared', src_root, distdir]) subprocess.check_call(['git', 'clone', '--shared', src_root, distdir])
process_submodules(distdir) process_submodules(distdir)
del_gitfiles(distdir) del_gitfiles(distdir)
run_dist_scripts(distdir, dist_scripts)
xzname = distdir + '.tar.xz' xzname = distdir + '.tar.xz'
# Should use shutil but it got xz support only in 3.5. # Should use shutil but it got xz support only in 3.5.
with tarfile.open(xzname, 'w:xz') as tf: with tarfile.open(xzname, 'w:xz') as tf:
@ -92,12 +111,14 @@ def create_dist_git(dist_name, src_root, bld_root, dist_sub):
return (xzname, ) return (xzname, )
def create_dist_hg(dist_name, src_root, bld_root, dist_sub): def create_dist_hg(dist_name, src_root, bld_root, dist_sub, dist_scripts):
os.makedirs(dist_sub, exist_ok=True) os.makedirs(dist_sub, exist_ok=True)
tarname = os.path.join(dist_sub, dist_name + '.tar') tarname = os.path.join(dist_sub, dist_name + '.tar')
xzname = tarname + '.xz' xzname = tarname + '.xz'
subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'tar', tarname]) subprocess.check_call(['hg', 'archive', '-R', src_root, '-S', '-t', 'tar', tarname])
if len(dist_scripts) > 0:
print('WARNING: dist scripts not supported in Mercurial projects.')
with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf: with lzma.open(xzname, 'wb') as xf, open(tarname, 'rb') as tf:
shutil.copyfileobj(tf, xf) shutil.copyfileobj(tf, xf)
os.unlink(tarname) os.unlink(tarname)
@ -152,9 +173,9 @@ def run(args):
dist_name = build.project_name + '-' + build.project_version dist_name = build.project_name + '-' + build.project_version
if os.path.isdir(os.path.join(src_root, '.git')): if os.path.isdir(os.path.join(src_root, '.git')):
names = create_dist_git(dist_name, src_root, bld_root, dist_sub) names = create_dist_git(dist_name, src_root, bld_root, dist_sub, build.dist_scripts)
elif os.path.isdir(os.path.join(src_root, '.hg')): elif os.path.isdir(os.path.join(src_root, '.hg')):
names = create_dist_hg(dist_name, src_root, bld_root, dist_sub) names = create_dist_hg(dist_name, src_root, bld_root, dist_sub, build.dist_scripts)
else: else:
print('Dist currently only works with Git or Mercurial repos.') print('Dist currently only works with Git or Mercurial repos.')
return 1 return 1

@ -85,6 +85,17 @@ def is_ci():
return True return True
return False return False
def _git_init(project_dir):
subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL)
subprocess.check_call(['git', 'config',
'user.name', 'Author Person'], cwd=project_dir)
subprocess.check_call(['git', 'config',
'user.email', 'teh_coderz@example.com'], cwd=project_dir)
subprocess.check_call('git add *', cwd=project_dir, shell=True,
stdout=subprocess.DEVNULL)
subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir,
stdout=subprocess.DEVNULL)
def skipIfNoPkgconfig(f): def skipIfNoPkgconfig(f):
''' '''
Skip this test if no pkg-config is found, unless we're on Travis or Skip this test if no pkg-config is found, unless we're on Travis or
@ -690,11 +701,19 @@ class DataTests(unittest.TestCase):
if f.parts[-1].endswith('~'): if f.parts[-1].endswith('~'):
continue continue
if f.suffix == '.md': if f.suffix == '.md':
in_code_block = False
with f.open() as snippet: with f.open() as snippet:
for line in snippet: for line in snippet:
if line.startswith(' '):
continue
if line.startswith('```'):
in_code_block = not in_code_block
if in_code_block:
continue
m = re.match(hashcounter, line) m = re.match(hashcounter, line)
if m: if m:
self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name) self.assertEqual(len(m.group(0)), 2, 'All headings in snippets must have two hash symbols: ' + f.name)
self.assertFalse(in_code_block, 'Unclosed code block.')
else: else:
if f.name != 'add_release_note_snippets_here': if f.name != 'add_release_note_snippets_here':
self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name) self.assertTrue(False, 'A file without .md suffix in snippets dir: ' + f.name)
@ -1712,19 +1731,8 @@ class AllPlatformTests(BasePlatformTests):
if not shutil.which('git'): if not shutil.which('git'):
raise unittest.SkipTest('Git not found') raise unittest.SkipTest('Git not found')
def git_init(project_dir):
subprocess.check_call(['git', 'init'], cwd=project_dir, stdout=subprocess.DEVNULL)
subprocess.check_call(['git', 'config',
'user.name', 'Author Person'], cwd=project_dir)
subprocess.check_call(['git', 'config',
'user.email', 'teh_coderz@example.com'], cwd=project_dir)
subprocess.check_call(['git', 'add', 'meson.build', 'distexe.c'], cwd=project_dir,
stdout=subprocess.DEVNULL)
subprocess.check_call(['git', 'commit', '-a', '-m', 'I am a project'], cwd=project_dir,
stdout=subprocess.DEVNULL)
try: try:
self.dist_impl(git_init) self.dist_impl(_git_init)
except PermissionError: except PermissionError:
# When run under Windows CI, something (virus scanner?) # When run under Windows CI, something (virus scanner?)
# holds on to the git files so cleaning up the dir # holds on to the git files so cleaning up the dir
@ -1753,6 +1761,24 @@ class AllPlatformTests(BasePlatformTests):
# fails sometimes. # fails sometimes.
pass pass
def test_dist_git_script(self):
if not shutil.which('git'):
raise unittest.SkipTest('Git not found')
try:
with tempfile.TemporaryDirectory() as tmpdir:
project_dir = os.path.join(tmpdir, 'a')
shutil.copytree(os.path.join(self.unit_test_dir, '35 dist script'),
project_dir)
_git_init(project_dir)
self.init(project_dir)
self.build('dist')
except PermissionError:
# When run under Windows CI, something (virus scanner?)
# holds on to the git files so cleaning up the dir
# fails sometimes.
pass
def dist_impl(self, vcs_init): def dist_impl(self, vcs_init):
# Create this on the fly because having rogue .git directories inside # Create this on the fly because having rogue .git directories inside
# the source tree leads to all kinds of trouble. # the source tree leads to all kinds of trouble.

@ -0,0 +1,7 @@
project('dist script', 'c',
version : '1.0.0')
exe = executable('comparer', 'prog.c')
test('compare', exe)
meson.add_dist_script('replacer.py')

@ -0,0 +1,7 @@
#include<string.h>
#define REPLACEME "incorrect"
int main(int argc, char **argv) {
return strcmp(REPLACEME, "correct");
}

@ -0,0 +1,12 @@
#!/usr/bin/env python3
import os, sys
import pathlib
source_root = pathlib.Path(os.environ['MESON_DIST_ROOT'])
modfile = source_root / 'prog.c'
contents = modfile.read_text()
contents = contents.replace('"incorrect"', '"correct"')
modfile.write_text(contents)
Loading…
Cancel
Save