Skip to content

Commit 8e06ab6

Browse files
committed
Use a script to clean-up custom-target output dirs
The script will manually delete all custom_target outputs that are directories instead of files. This is needed because on platforms other than Windows, Ninja only deletes directories while cleaning if they are empty. Closes #1220
1 parent c854ae1 commit 8e06ab6

File tree

4 files changed

+89
-5
lines changed

4 files changed

+89
-5
lines changed

mesonbuild/backend/backends.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@
2222
import subprocess
2323
from ..mesonlib import MesonException, get_compiler_for_source, classify_unity_sources
2424

25+
class CleanTrees():
26+
'''
27+
Directories outputted by custom targets that have to be manually cleaned
28+
because on Linux `ninja clean` only deletes empty directories.
29+
'''
30+
def __init__(self, build_dir, trees):
31+
self.build_dir = build_dir
32+
self.trees = trees
33+
2534
class InstallData():
2635
def __init__(self, source_dir, build_dir, prefix):
2736
self.source_dir = source_dir

mesonbuild/backend/ninjabackend.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .. import dependencies
2121
from .. import compilers
2222
from ..mesonlib import File, MesonException, get_compiler_for_source, Popen_safe
23-
from .backends import InstallData
23+
from .backends import CleanTrees, InstallData
2424
from ..build import InvalidArguments
2525
import os, sys, pickle, re
2626
import subprocess, shutil
@@ -2109,6 +2109,22 @@ def generate_shlib_aliases(self, target, outdir):
21092109
except OSError:
21102110
mlog.debug("Library versioning disabled because we do not have symlink creation privileges.")
21112111

2112+
def generate_custom_target_clean(self, outfile, trees):
2113+
e = NinjaBuildElement(self.all_outputs, 'clean-ctlist', 'CUSTOM_COMMAND', 'PHONY')
2114+
d = CleanTrees(self.environment.get_build_dir(), trees)
2115+
d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat')
2116+
script_root = self.environment.get_script_dir()
2117+
clean_script = os.path.join(script_root, 'cleantrees.py')
2118+
e.add_item('COMMAND', [sys.executable,
2119+
self.environment.get_build_command(),
2120+
'--internal', 'cleantrees', d_file])
2121+
e.add_item('description', 'Cleaning CustomTarget directories')
2122+
e.write(outfile)
2123+
# Write out the data file passed to the script
2124+
with open(d_file, 'wb') as ofile:
2125+
pickle.dump(d, ofile)
2126+
return 'clean-ctlist'
2127+
21122128
def generate_gcov_clean(self, outfile):
21132129
gcno_elem = NinjaBuildElement(self.all_outputs, 'clean-gcno', 'CUSTOM_COMMAND', 'PHONY')
21142130
script_root = self.environment.get_script_dir()
@@ -2136,14 +2152,19 @@ def generate_utils(self, outfile):
21362152

21372153
def generate_ending(self, outfile):
21382154
targetlist = []
2155+
ctlist = []
21392156
for t in self.build.get_targets().values():
21402157
# RunTargets are meant to be invoked manually
21412158
if isinstance(t, build.RunTarget):
21422159
continue
2143-
# CustomTargets that aren't installed should only be built if they
2144-
# are used by something else or are meant to be always built
2145-
if isinstance(t, build.CustomTarget) and not (t.install or t.build_always):
2146-
continue
2160+
if isinstance(t, build.CustomTarget):
2161+
# Create a list of all custom target outputs
2162+
for o in t.get_outputs():
2163+
ctlist.append(os.path.join(self.get_target_dir(t), o))
2164+
# CustomTargets that aren't installed should only be built if
2165+
# they are used by something else or are to always be built
2166+
if not (t.install or t.build_always):
2167+
continue
21472168
# Add the first output of each target to the 'all' target so that
21482169
# they are all built
21492170
targetlist.append(os.path.join(self.get_target_dir(t), t.get_outputs()[0]))
@@ -2160,6 +2181,14 @@ def generate_ending(self, outfile):
21602181
elem = NinjaBuildElement(self.all_outputs, 'clean', 'CUSTOM_COMMAND', 'PHONY')
21612182
elem.add_item('COMMAND', [ninja_command, '-t', 'clean'])
21622183
elem.add_item('description', 'Cleaning')
2184+
# If we have custom targets in this project, add all their outputs to
2185+
# the list that is passed to the `cleantrees.py` script. The script
2186+
# will manually delete all custom_target outputs that are directories
2187+
# instead of files. This is needed because on platforms other than
2188+
# Windows, Ninja only deletes directories while cleaning if they are
2189+
# empty. https://github.com/mesonbuild/meson/issues/1220
2190+
if ctlist:
2191+
elem.add_dep(self.generate_custom_target_clean(outfile, ctlist))
21632192
if 'b_coverage' in self.environment.coredata.base_options and \
21642193
self.environment.coredata.base_options['b_coverage'].value:
21652194
self.generate_gcov_clean(outfile)

mesonbuild/mesonmain.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,9 @@ def run_script_command(args):
198198
if cmdname == 'exe':
199199
import mesonbuild.scripts.meson_exe as abc
200200
cmdfunc = abc.run
201+
elif cmdname == 'cleantrees':
202+
import mesonbuild.scripts.cleantrees as abc
203+
cmdfunc = abc.run
201204
elif cmdname == 'install':
202205
import mesonbuild.scripts.meson_install as abc
203206
cmdfunc = abc.run

mesonbuild/scripts/cleantrees.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2016 The Meson development team
2+
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import os
16+
import sys
17+
import shutil
18+
import pickle
19+
20+
def rmtrees(build_dir, trees):
21+
for t in trees:
22+
# Never delete trees outside of the builddir
23+
if os.path.isabs(t):
24+
print('Cannot delete dir with absolute path {!r}'.format(t))
25+
continue
26+
bt = os.path.join(build_dir, t)
27+
# Skip if it doesn't exist, or if it is not a directory
28+
if os.path.isdir(bt):
29+
shutil.rmtree(bt, ignore_errors=True)
30+
31+
def run(args):
32+
if len(args) != 1:
33+
print('Cleaner script for Meson. Do not run on your own please.')
34+
print('cleantrees.py <data-file>')
35+
return 1
36+
with open(args[0], 'rb') as f:
37+
data = pickle.load(f)
38+
rmtrees(data.build_dir, data.trees)
39+
# Never fail cleaning
40+
return 0
41+
42+
if __name__ == '__main__':
43+
run(sys.argv[1:])

0 commit comments

Comments
 (0)