Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for failing on file conflicts. #683

Merged
merged 3 commits into from
Sep 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/install.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def _pkg_install_script_impl(ctx):
content_map = {}
for src in ctx.attr.srcs:
process_src(
ctx,
content_map,
files_to_run,
src = src,
Expand Down
70 changes: 45 additions & 25 deletions pkg/private/pkg_files.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ _DestFile = provider(
},
)

def _check_dest(content_map, dest, src, origin):
def _check_dest(ctx, content_map, dest, src, origin):
old_entry = content_map.get(dest)
if not old_entry:
return
Expand All @@ -73,23 +73,28 @@ def _check_dest(content_map, dest, src, origin):
# people specify the owner in one place, but another overly broad glob
# brings in the file with a different owner.
if old_entry.src.path != src.path:
# buildifier: disable=print
print(
"Duplicate output path: <%s>, declared in %s and %s" % (
msg = "Duplicate output path: <%s>, declared in %s and %s\n SRC: %s" % (
dest,
origin,
content_map[dest].origin,
),
"\n SRC:",
src,
)
src,
)
if ctx.attr.allow_duplicates_with_different_content:
# buildifier: disable=print
print("WARNING:", msg)
else:
# When we default to this behaviour, we should consider telling
# users the attribute to set to deal with this.
# For now though, let's not, since they've explicitly opted in.
fail(msg)

def _merge_attributes(info, mode, user, group, uid, gid):
if hasattr(info, "attributes"):
attrs = info.attributes
mode = attrs.get("mode") or mode
user = attrs.get("user") or user
group = attrs.get("group") or group

new_uid = attrs.get("uid")
if new_uid != None:
uid = new_uid
Expand All @@ -98,11 +103,11 @@ def _merge_attributes(info, mode, user, group, uid, gid):
gid = new_gid
return (mode, user, group, uid, gid)

def _process_pkg_dirs(content_map, pkg_dirs_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
def _process_pkg_dirs(ctx, content_map, pkg_dirs_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
attrs = _merge_attributes(pkg_dirs_info, default_mode, default_user, default_group, default_uid, default_gid)
for dir in pkg_dirs_info.dirs:
dest = dir.strip("/")
_check_dest(content_map, dest, None, origin)
_check_dest(ctx, content_map, dest, None, origin)
content_map[dest] = _DestFile(
src = None,
entry_type = ENTRY_IS_DIR,
Expand All @@ -114,11 +119,11 @@ def _process_pkg_dirs(content_map, pkg_dirs_info, origin, default_mode, default_
origin = origin,
)

def _process_pkg_files(content_map, pkg_files_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
def _process_pkg_files(ctx, content_map, pkg_files_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
attrs = _merge_attributes(pkg_files_info, default_mode, default_user, default_group, default_uid, default_gid)
for filename, src in pkg_files_info.dest_src_map.items():
dest = filename.strip("/")
_check_dest(content_map, dest, src, origin)
_check_dest(ctx, content_map, dest, src, origin)
content_map[dest] = _DestFile(
src = src,
entry_type = ENTRY_IS_TREE if src.is_directory else ENTRY_IS_FILE,
Expand All @@ -130,10 +135,10 @@ def _process_pkg_files(content_map, pkg_files_info, origin, default_mode, defaul
origin = origin,
)

def _process_pkg_symlink(content_map, pkg_symlink_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
def _process_pkg_symlink(ctx, content_map, pkg_symlink_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
dest = pkg_symlink_info.destination.strip("/")
attrs = _merge_attributes(pkg_symlink_info, default_mode, default_user, default_group, default_uid, default_gid)
_check_dest(content_map, dest, None, origin)
_check_dest(ctx, content_map, dest, None, origin)
content_map[dest] = _DestFile(
src = None,
entry_type = ENTRY_IS_LINK,
Expand All @@ -146,18 +151,19 @@ def _process_pkg_symlink(content_map, pkg_symlink_info, origin, default_mode, de
link_to = pkg_symlink_info.target,
)

def _process_pkg_filegroup(content_map, pkg_filegroup_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
def _process_pkg_filegroup(ctx, content_map, pkg_filegroup_info, origin, default_mode, default_user, default_group, default_uid, default_gid):
if hasattr(pkg_filegroup_info, "pkg_dirs"):
for d in pkg_filegroup_info.pkg_dirs:
_process_pkg_dirs(content_map, d[0], d[1], default_mode, default_user, default_group, default_uid, default_gid)
_process_pkg_dirs(ctx, content_map, d[0], d[1], default_mode, default_user, default_group, default_uid, default_gid)
if hasattr(pkg_filegroup_info, "pkg_files"):
for pf in pkg_filegroup_info.pkg_files:
_process_pkg_files(content_map, pf[0], pf[1], default_mode, default_user, default_group, default_uid, default_gid)
_process_pkg_files(ctx, content_map, pf[0], pf[1], default_mode, default_user, default_group, default_uid, default_gid)
if hasattr(pkg_filegroup_info, "pkg_symlinks"):
for psl in pkg_filegroup_info.pkg_symlinks:
_process_pkg_symlink(content_map, psl[0], psl[1], default_mode, default_user, default_group, default_uid, default_gid)
_process_pkg_symlink(ctx, content_map, psl[0], psl[1], default_mode, default_user, default_group, default_uid, default_gid)

def process_src(
ctx,
content_map,
files,
src,
Expand Down Expand Up @@ -191,6 +197,7 @@ def process_src(
found_info = False
if PackageFilesInfo in src:
_process_pkg_files(
ctx,
content_map,
src[PackageFilesInfo],
origin,
Expand All @@ -203,6 +210,7 @@ def process_src(
found_info = True
if PackageFilegroupInfo in src:
_process_pkg_filegroup(
ctx,
content_map,
src[PackageFilegroupInfo],
origin,
Expand All @@ -215,6 +223,7 @@ def process_src(
found_info = True
if PackageSymlinkInfo in src:
_process_pkg_symlink(
ctx,
content_map,
src[PackageSymlinkInfo],
origin,
Expand All @@ -227,6 +236,7 @@ def process_src(
found_info = True
if PackageDirsInfo in src:
_process_pkg_dirs(
ctx,
content_map,
src[PackageDirsInfo],
origin,
Expand Down Expand Up @@ -261,10 +271,11 @@ def add_directory(content_map, dir_path, origin, mode = None, user = None, group
gid = gid,
)

def add_empty_file(content_map, dest_path, origin, mode = None, user = None, group = None, uid = None, gid = None):
def add_empty_file(ctx, content_map, dest_path, origin, mode = None, user = None, group = None, uid = None, gid = None):
"""Add a single file to the content map.

Args:
ctx: rule context.
content_map: The content map
dest_path: Where to place the file in the package.
origin: The rule instance adding this entry
Expand All @@ -273,7 +284,7 @@ def add_empty_file(content_map, dest_path, origin, mode = None, user = None, gro
group: fallback mode to use for Package*Info elements without group
"""
dest = dest_path.strip("/")
_check_dest(content_map, dest, None, origin)
_check_dest(ctx, content_map, dest, None, origin)
content_map[dest] = _DestFile(
src = None,
entry_type = ENTRY_IS_EMPTY_FILE,
Expand Down Expand Up @@ -323,6 +334,7 @@ def add_label_list(

for src in srcs:
if not process_src(
ctx,
content_map,
file_deps,
src = src,
Expand All @@ -335,6 +347,7 @@ def add_label_list(
):
# Add in the files of srcs which are not pkg_* types
add_from_default_info(
ctx,
content_map,
file_deps,
src,
Expand All @@ -349,6 +362,7 @@ def add_label_list(
)

def add_from_default_info(
ctx,
content_map,
file_deps,
src,
Expand All @@ -363,6 +377,7 @@ def add_from_default_info(
"""Helper method to add the DefaultInfo of a target to a content_map.

Args:
ctx: rule context.
content_map: (r/w) The content map to update.
file_deps: (r/w) The list of file Depsets that srcs depend on.
src: A source object.
Expand Down Expand Up @@ -394,6 +409,7 @@ def add_from_default_info(
else:
fmode = "0755" if f == the_executable else default_mode
add_single_file(
ctx,
content_map,
dest_path = d_path,
src = f,
Expand All @@ -409,7 +425,7 @@ def add_from_default_info(
for rf in runfiles.files.to_list():
d_path = base_path + "/" + rf.short_path
fmode = "0755" if rf == the_executable else default_mode
_check_dest(content_map, d_path, rf, src.label)
_check_dest(ctx, content_map, d_path, rf, src.label)
content_map[d_path] = _DestFile(
src = rf,
entry_type = ENTRY_IS_FILE,
Expand Down Expand Up @@ -450,10 +466,12 @@ def get_my_executable(src):
return ftr.executable
return None

def add_single_file(content_map, dest_path, src, origin, mode = None, user = None, group = None, uid = None, gid = None):

def add_single_file(ctx, content_map, dest_path, src, origin, mode = None, user = None, group = None, uid = None, gid = None):
"""Add an single file to the content map.

Args:
ctx: rule context.
content_map: The content map
dest_path: Where to place the file in the package.
src: Source object. Must have len(src[DefaultInfo].files) == 1
Expand All @@ -463,7 +481,7 @@ def add_single_file(content_map, dest_path, src, origin, mode = None, user = Non
group: fallback mode to use for Package*Info elements without group
"""
dest = dest_path.strip("/")
_check_dest(content_map, dest, src, origin)
_check_dest(ctx, content_map, dest, src, origin)
content_map[dest] = _DestFile(
src = src,
entry_type = ENTRY_IS_FILE,
Expand All @@ -475,10 +493,12 @@ def add_single_file(content_map, dest_path, src, origin, mode = None, user = Non
gid = gid,
)

def add_symlink(content_map, dest_path, src, origin, mode = None, user = None, group = None, uid = None, gid = None):
def add_symlink(ctx, content_map, dest_path, src, origin, mode = None, user = None, group = None, uid = None, gid = None):

"""Add a symlink to the content map.

Args:
ctx: rule context.
content_map: The content map
dest_path: Where to place the file in the package.
src: Path to link to.
Expand All @@ -488,7 +508,7 @@ def add_symlink(content_map, dest_path, src, origin, mode = None, user = None, g
group: fallback mode to use for Package*Info elements without group
"""
dest = dest_path.strip("/")
_check_dest(content_map, dest, None, origin)
_check_dest(ctx, content_map, dest, None, origin)
content_map[dest] = _DestFile(
src = None,
link_to = src,
Expand Down
20 changes: 18 additions & 2 deletions pkg/private/tar/tar.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def _pkg_tar_impl(ctx):
# Start with all the pkg_* inputs
for src in ctx.attr.srcs:
if not process_src(
ctx,
content_map,
file_deps,
src = src,
Expand Down Expand Up @@ -149,7 +150,12 @@ def _pkg_tar_impl(ctx):
if f.is_directory:
add_tree_artifact(content_map, dest, f, src.label)
else:
add_single_file(content_map, dest, f, src.label)
# Note: This extra remap is the bottleneck preventing this
# large block from being a utility method as shown below.
# Should we disallow mixing pkg_files in srcs with remap?
# I am fine with that if it makes the code more readable.
dest = _remap(remap_paths, d_path)
add_single_file(ctx, content_map, dest, f, src.label)

# TODO(aiuto): I want the code to look like this, but we don't have lambdas.
# transform_path = lambda f: _remap(
Expand All @@ -164,6 +170,7 @@ def _pkg_tar_impl(ctx):
fail("Each input must describe exactly one file.", attr = "files")
file_deps.append(depset([target_files[0]]))
add_single_file(
ctx,
content_map,
f_dest_path,
target_files[0],
Expand All @@ -183,13 +190,14 @@ def _pkg_tar_impl(ctx):
"%s=%s" % (_quote(key), ctx.attr.ownernames[key]),
)
for empty_file in ctx.attr.empty_files:
add_empty_file(content_map, empty_file, ctx.label)
add_empty_file(ctx, content_map, empty_file, ctx.label)
for empty_dir in ctx.attr.empty_dirs or []:
add_directory(content_map, empty_dir, ctx.label)
for f in ctx.files.deps:
args.add("--tar", f.path)
for link in ctx.attr.symlinks:
add_symlink(
ctx,
content_map,
link,
ctx.attr.symlinks[link],
Expand Down Expand Up @@ -279,6 +287,14 @@ The name may contain variables, same as [package_file_name](#package_file_name)"
doc = "See [Common Attributes](#package_variables)",
providers = [PackageVariablesInfo],
),
"allow_duplicates_with_different_content": attr.bool(
default=True,
doc="""If true, will allow you to reference multiple pkg_* which conflict
(writing different content or metadata to the same destination).
Such behaviour is always incorrect, but we provide a flag to support it in case old
builds were accidentally doing it. Never explicitly set this to true for new code.
"""
),
"stamp": attr.int(
doc = """Enable file time stamping. Possible values:
<li>stamp = 1: Use the time of the build as the modification time of each file in the archive.
Expand Down
8 changes: 8 additions & 0 deletions pkg/private/zip/zip.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ The list of compressions is the same as Python's ZipFile: https://docs.python.or
default = 0,
),

"allow_duplicates_with_different_content": attr.bool(
default=True,
doc="""If true, will allow you to reference multiple pkg_* which conflict
(writing different content or metadata to the same destination).
Such behaviour is always incorrect, but we provide a flag to support it in case old
builds were accidentally doing it. Never explicitly set this to true for new code.
"""
),
# Is --stamp set on the command line?
# TODO(https://github.com/bazelbuild/rules_pkg/issues/340): Remove this.
"private_stamp_detect": attr.bool(default = False),
Expand Down