Skip to content

Commit 4e60992

Browse files
rickeylevcopybara-github
authored andcommitted
python: Add repo mapping to zip builds so runfiles lookup works correctly
To make runfiles lookups work correctly with bzlmod, a `_repo_mapping` file is used to map apparent names to canonical names. This file is normally automatically created by higher level code and injected into the runfiles, but that happens after a rule has finished running. This means it isn't in the runfiles object that py_binary itself sees when its constructing a zip file of itself. To fix, the zip file generates a repo mapping itself and puts it into the zip file, thus allowing the runfiles library to find it when run from the zip file. This requires a Java hook since the underlying `RepoMappingManifestAction` isn't directly exposed to Starlark. Fixes #17941 PiperOrigin-RevId: 532265478 Change-Id: I5f36fe58f043611481a7ed6ba19a7bbf5be4edf2
1 parent f1de4ae commit 4e60992

File tree

6 files changed

+91
-0
lines changed

6 files changed

+91
-0
lines changed

src/main/java/com/google/devtools/build/lib/rules/python/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ java_library(
3535
"//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/patch_transition",
3636
"//src/main/java/com/google/devtools/build/lib/analysis:config/transitions/transition_factory",
3737
"//src/main/java/com/google/devtools/build/lib/analysis:file_provider",
38+
"//src/main/java/com/google/devtools/build/lib/analysis:repo_mapping_manifest_action",
3839
"//src/main/java/com/google/devtools/build/lib/analysis:rule_definition_environment",
3940
"//src/main/java/com/google/devtools/build/lib/analysis:transitive_info_collection",
4041
"//src/main/java/com/google/devtools/build/lib/analysis:transitive_info_provider",
@@ -44,6 +45,7 @@ java_library(
4445
"//src/main/java/com/google/devtools/build/lib/concurrent",
4546
"//src/main/java/com/google/devtools/build/lib/events",
4647
"//src/main/java/com/google/devtools/build/lib/packages",
48+
"//src/main/java/com/google/devtools/build/lib/packages/semantics",
4749
"//src/main/java/com/google/devtools/build/lib/rules/cpp",
4850
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec",
4951
"//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:serialization-constant",

src/main/java/com/google/devtools/build/lib/rules/python/PyBuiltins.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.google.devtools.build.lib.analysis.AliasProvider;
2626
import com.google.devtools.build.lib.analysis.FileProvider;
2727
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
28+
import com.google.devtools.build.lib.analysis.RepoMappingManifestAction;
2829
import com.google.devtools.build.lib.analysis.Runfiles;
2930
import com.google.devtools.build.lib.analysis.RunfilesSupport;
3031
import com.google.devtools.build.lib.analysis.SingleRunfilesSupplier;
@@ -40,6 +41,7 @@
4041
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
4142
import com.google.devtools.build.lib.collect.nestedset.Order;
4243
import com.google.devtools.build.lib.packages.StarlarkProvider;
44+
import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions;
4345
import com.google.devtools.build.lib.util.Fingerprint;
4446
import com.google.devtools.build.lib.util.OS;
4547
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -66,6 +68,20 @@ protected PyBuiltins(Runfiles.EmptyFilesSupplier emptyFilesSupplier) {
6668
this.emptyFilesSupplier = emptyFilesSupplier;
6769
}
6870

71+
@StarlarkMethod(
72+
name = "is_bzlmod_enabled",
73+
doc = "Tells if bzlmod is enabled",
74+
parameters = {
75+
@Param(name = "ctx", positional = true, named = true, defaultValue = "unbound")
76+
})
77+
public boolean isBzlmodEnabled(StarlarkRuleContext starlarkCtx) {
78+
return starlarkCtx
79+
.getRuleContext()
80+
.getAnalysisEnvironment()
81+
.getStarlarkSemantics()
82+
.getBool(BuildLanguageOptions.ENABLE_BZLMOD);
83+
}
84+
6985
@StarlarkMethod(
7086
name = "is_singleton_depset",
7187
doc = "Efficiently checks if the depset is a singleton.",
@@ -288,6 +304,28 @@ public Object newEmptyRunfilesWithMiddleman(
288304
.build();
289305
}
290306

307+
@StarlarkMethod(
308+
name = "create_repo_mapping_manifest",
309+
doc = "Write a repo_mapping file for the given runfiles",
310+
parameters = {
311+
@Param(name = "ctx", positional = false, named = true, defaultValue = "unbound"),
312+
@Param(name = "runfiles", positional = false, named = true, defaultValue = "unbound"),
313+
@Param(name = "output", positional = false, named = true, defaultValue = "unbound")
314+
})
315+
public void repoMappingAction(
316+
StarlarkRuleContext starlarkCtx, Runfiles runfiles, Artifact repoMappingManifest) {
317+
var ruleContext = starlarkCtx.getRuleContext();
318+
ruleContext
319+
.getAnalysisEnvironment()
320+
.registerAction(
321+
new RepoMappingManifestAction(
322+
ruleContext.getActionOwner(),
323+
repoMappingManifest,
324+
ruleContext.getTransitivePackagesForRunfileRepoMappingManifest(),
325+
runfiles.getAllArtifacts(),
326+
ruleContext.getWorkspaceName()));
327+
}
328+
291329
@StarlarkMethod(
292330
name = "merge_runfiles_with_generated_inits_empty_files_supplier",
293331
doc =

src/main/starlark/builtins_bzl/common/python/py_executable_bazel.bzl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,24 @@ def _create_zip_file(ctx, *, output, original_nonzip_executable, executable_for_
372372
return None
373373

374374
manifest.add_all(runfiles.files, map_each = map_zip_runfiles, allow_closure = True)
375+
375376
inputs = [executable_for_zip_file]
377+
if _py_builtins.is_bzlmod_enabled(ctx):
378+
zip_repo_mapping_manifest = ctx.actions.declare_file(
379+
output.basename + ".repo_mapping",
380+
sibling = output,
381+
)
382+
_py_builtins.create_repo_mapping_manifest(
383+
ctx = ctx,
384+
runfiles = runfiles,
385+
output = zip_repo_mapping_manifest,
386+
)
387+
manifest.add("{}/_repo_mapping={}".format(
388+
_ZIP_RUNFILES_DIRECTORY_NAME,
389+
zip_repo_mapping_manifest.path,
390+
))
391+
inputs.append(zip_repo_mapping_manifest)
392+
376393
for artifact in runfiles.files.to_list():
377394
# Don't include the original executable because it isn't used by the
378395
# zip file, so no need to build it for the action.

src/main/starlark/builtins_bzl/common/python/py_internal.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ py_internal = struct(
2828
add_py_extra_pseudo_action = _py_builtins.add_py_extra_pseudo_action,
2929
are_action_listeners_enabled = _py_builtins.are_action_listeners_enabled,
3030
copy_without_caching = _py_builtins.copy_without_caching,
31+
create_repo_mapping_manifest = _py_builtins.create_repo_mapping_manifest,
3132
create_sources_only_manifest = _py_builtins.create_sources_only_manifest,
3233
declare_constant_metadata_file = _py_builtins.declare_constant_metadata_file,
3334
expand_location_and_make_variables = _py_builtins.expand_location_and_make_variables,
@@ -36,6 +37,7 @@ py_internal = struct(
3637
get_legacy_exernal_runfiles = _py_builtins.get_legacy_external_runfiles,
3738
get_rule_name = _py_builtins.get_rule_name,
3839
is_available_for = _is_available_for,
40+
is_bzlmod_enabled = _py_builtins.is_bzlmod_enabled,
3941
is_singleton_depset = _py_builtins.is_singleton_depset,
4042
make_runfiles_respect_legacy_external_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles,
4143
merge_runfiles_with_generated_inits_empty_files_supplier = _py_builtins.merge_runfiles_with_generated_inits_empty_files_supplier,

src/test/shell/bazel/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@ sh_test(
917917
":test-deps",
918918
"@bazel_tools//tools/bash/runfiles",
919919
],
920+
tags = ["requires-network"],
920921
)
921922

922923
sh_test(

src/test/shell/bazel/python_version_test.sh

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,37 @@ EOF
191191
expect_log "I am mockpy!"
192192
}
193193

194+
# Verify that looking up runfiles that require repo mapping works
195+
function test_build_python_zip_bzlmod_repo_mapping_runfiles() {
196+
cat > WORKSPACE
197+
cat > MODULE.bazel << EOF
198+
module(name="pyzip")
199+
bazel_dep(name = "rules_python", version = "0.19.0")
200+
EOF
201+
mkdir test
202+
cat > test/BUILD << EOF
203+
py_binary(
204+
name = "pybin",
205+
srcs = ["pybin.py"],
206+
deps = ["@rules_python//python/runfiles"],
207+
data = ["data.txt"],
208+
)
209+
EOF
210+
echo "data" > test/data.txt
211+
cat > test/pybin.py << EOF
212+
from python.runfiles import runfiles
213+
rf = runfiles.Create()
214+
path = rf.Rlocation("pyzip/test/data.txt")
215+
with open(path, "r") as fp:
216+
fp.read()
217+
EOF
218+
219+
bazel run --enable_bzlmod --build_python_zip //test:pybin &> $TEST_log || fail "bazel run failed"
220+
221+
unzip -p bazel-bin/test/pybin.zip runfiles/_repo_mapping > actual_repo_mapping
222+
assert_contains ",pyzip,_main" actual_repo_mapping
223+
}
224+
194225
# Test that running a zip app without RUN_UNDER_RUNFILES=1 removes the
195226
# temporary directory it creates
196227
function test_build_python_zip_cleans_up_temporary_module_space() {

0 commit comments

Comments
 (0)