Skip to content

Commit 6c89303

Browse files
ShreeM01rickeylev
andauthored
python: Remove temporary module space created for zip-based binaries (bazelbuild#17764)
PR bazelbuild#15590 accidentally introduced a bug where zip-based binaries weren't cleaning up the temporary runfiles directory they extracted their zip into when they were run outside of a test invocation. To fix, explicitly keep track of when the module space needs to be deleted or not, and pass that long to the code that decides how to execute the program and how to clean it up afterwards. Fixes bazelbuild#17342 PiperOrigin-RevId: 513256904 Change-Id: I8c3d322248f734a6290a8a7ee5c8725fae5203dc Co-authored-by: Richard Levasseur <[email protected]>
1 parent c9e3eeb commit 6c89303

File tree

2 files changed

+44
-9
lines changed

2 files changed

+44
-9
lines changed

src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt

+18-9
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ def ExtractZip(zip_path, dest_dir):
202202
def CreateModuleSpace():
203203
temp_dir = tempfile.mkdtemp('', 'Bazel.runfiles_')
204204
ExtractZip(os.path.dirname(__file__), temp_dir)
205+
# IMPORTANT: Later code does `rm -fr` on dirname(module_space) -- it's
206+
# important that deletion code be in sync with this directory structure
205207
return os.path.join(temp_dir, 'runfiles')
206208

207209
# Returns repository roots to add to the import path.
@@ -301,7 +303,7 @@ def UnresolveSymlinks(output_filename):
301303
os.unlink(unfixed_file)
302304

303305
def ExecuteFile(python_program, main_filename, args, env, module_space,
304-
coverage_entrypoint, workspace):
306+
coverage_entrypoint, workspace, delete_module_space):
305307
# type: (str, str, list[str], dict[str, str], str, str|None, str|None) -> ...
306308
"""Executes the given Python file using the various environment settings.
307309

@@ -316,8 +318,9 @@ def ExecuteFile(python_program, main_filename, args, env, module_space,
316318
module_space: (str) Path to the module space/runfiles tree directory
317319
coverage_entrypoint: (str|None) Path to the coverage tool entry point file.
318320
workspace: (str|None) Name of the workspace to execute in. This is expected to be a
319-
directory under the runfiles tree, and will recursively delete the
320-
runfiles directory if set.
321+
directory under the runfiles tree.
322+
delete_module_space: (bool), True if the module space should be deleted
323+
after a successful (exit code zero) program run, False if not.
321324
"""
322325
# We want to use os.execv instead of subprocess.call, which causes
323326
# problems with signal passing (making it difficult to kill
@@ -327,16 +330,15 @@ def ExecuteFile(python_program, main_filename, args, env, module_space,
327330
# - On Windows, os.execv doesn't handle arguments with spaces
328331
# correctly, and it actually starts a subprocess just like
329332
# subprocess.call.
330-
# - When running in a workspace (i.e., if we're running from a zip),
331-
# we need to clean up the workspace after the process finishes so
332-
# control must return here.
333+
# - When running in a workspace or zip file, we need to clean up the
334+
# workspace after the process finishes so control must return here.
333335
# - If we may need to emit a host config warning after execution, we
334336
# can't execv because we need control to return here. This only
335337
# happens for targets built in the host config.
336338
# - For coverage targets, at least coveragepy requires running in
337339
# two invocations, which also requires control to return here.
338340
#
339-
if not (IsWindows() or workspace or coverage_entrypoint):
341+
if not (IsWindows() or workspace or coverage_entrypoint or delete_module_space):
340342
_RunExecv(python_program, main_filename, args, env)
341343

342344
if coverage_entrypoint is not None:
@@ -349,7 +351,10 @@ def ExecuteFile(python_program, main_filename, args, env, module_space,
349351
cwd=workspace
350352
)
351353

352-
if workspace:
354+
if delete_module_space:
355+
# NOTE: dirname() is called because CreateModuleSpace() creates a
356+
# sub-directory within a temporary directory, and we want to remove the
357+
# whole temporary directory.
353358
shutil.rmtree(os.path.dirname(module_space), True)
354359
sys.exit(ret_code)
355360

@@ -438,8 +443,10 @@ def Main():
438443

439444
if IsRunningFromZip():
440445
module_space = CreateModuleSpace()
446+
delete_module_space = True
441447
else:
442448
module_space = FindModuleSpace(main_rel_path)
449+
delete_module_space = False
443450

444451
python_imports = '%imports%'
445452
python_path_entries = CreatePythonPathEntries(python_imports, module_space)
@@ -524,9 +531,11 @@ def Main():
524531

525532
try:
526533
sys.stdout.flush()
534+
# NOTE: ExecuteFile may call execve() and lines after this will never run.
527535
ExecuteFile(
528536
python_program, main_filename, args, new_env, module_space,
529-
cov_tool, workspace
537+
cov_tool, workspace,
538+
delete_module_space = delete_module_space,
530539
)
531540

532541
except EnvironmentError:

src/test/shell/bazel/python_version_test.sh

+26
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,32 @@ EOF
201201
expect_log "I am mockpy!"
202202
}
203203

204+
# Test that running a zip app without RUN_UNDER_RUNFILES=1 removes the
205+
# temporary directory it creates
206+
function test_build_python_zip_cleans_up_temporary_module_space() {
207+
208+
mkdir test
209+
cat > test/BUILD << EOF
210+
py_binary(
211+
name = "pybin",
212+
srcs = ["pybin.py"],
213+
)
214+
EOF
215+
cat > test/pybin.py << EOF
216+
print(__file__)
217+
EOF
218+
219+
bazel build //test:pybin --build_python_zip &> $TEST_log || fail "bazel build failed"
220+
pybin_location=$(bazel-bin/test/pybin)
221+
222+
# The pybin location is "<ms root>/runfiles/<workspace>/test/pybin.py",
223+
# so we have to go up 4 directories to get to the module space root
224+
module_space_dir=$(dirname $(dirname $(dirname $(dirname "$pybin_location"))))
225+
if [[ -d "$module_space_dir" ]]; then
226+
fail "expected module space directory to be deleted, but $module_space_dir still exists"
227+
fi
228+
}
229+
204230
function test_get_python_zip_file_via_output_group() {
205231
touch foo.py
206232
cat > BUILD <<'EOF'

0 commit comments

Comments
 (0)