[llvm] [dsymutil] Add --embed-resource to copy files into dSYM bundles. (PR #190663)
Ryan Mansfield via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 6 12:45:36 PDT 2026
https://github.com/rjmansfield created https://github.com/llvm/llvm-project/pull/190663
Add a new --embed-resource flag that copies files or directories into the dSYM bundle's Contents/Resources/ directory during generation.
Projects often need to embed files such as LLDB Python scripts into dSYM bundles, and this is usually done with a post dsym generation script, which may race stripping and code signing steps.
rdar://50633614
>From 988e8364e4c18cc77bf795ec14f055ad3f82c7ca Mon Sep 17 00:00:00 2001
From: Ryan Mansfield <ryan_mansfield at apple.com>
Date: Mon, 6 Apr 2026 15:33:56 -0400
Subject: [PATCH] [dsymutil] Add --embed-resource to copy files into dSYM
bundles.
Add a new --embed-resource flag that copies files or directories into
the dSYM bundle's Contents/Resources/ directory during generation.
rdar://50633614
---
llvm/docs/CommandGuide/dsymutil.rst | 7 +++
llvm/test/tools/dsymutil/cmdline.test | 4 ++
llvm/test/tools/dsymutil/embed-resource.test | 65 ++++++++++++++++++++
llvm/tools/dsymutil/DwarfLinkerForBinary.cpp | 60 ++++++++++++++++++
llvm/tools/dsymutil/DwarfLinkerForBinary.h | 2 +
llvm/tools/dsymutil/LinkUtils.h | 3 +
llvm/tools/dsymutil/Options.td | 9 +++
llvm/tools/dsymutil/dsymutil.cpp | 27 ++++++++
8 files changed, 177 insertions(+)
create mode 100644 llvm/test/tools/dsymutil/embed-resource.test
diff --git a/llvm/docs/CommandGuide/dsymutil.rst b/llvm/docs/CommandGuide/dsymutil.rst
index c40cd18f32d19..9aa805cc6aa43 100644
--- a/llvm/docs/CommandGuide/dsymutil.rst
+++ b/llvm/docs/CommandGuide/dsymutil.rst
@@ -64,6 +64,13 @@ OPTIONS
for dSYM files with debug information about symbols present in those
libraries.
+.. option:: --embed-resource <src-path>=<bundle-relative-path>
+
+ Copy a file or directory into the dSYM bundle's ``Contents/Resources/``
+ directory. The argument is ``<source-path>=<destination-relative-to-Resources>``.
+ If the source is a directory, its contents are copied recursively. This option
+ can be specified multiple times.
+
.. option:: --fat64
Use a 64-bit header when emitting universal binaries.
diff --git a/llvm/test/tools/dsymutil/cmdline.test b/llvm/test/tools/dsymutil/cmdline.test
index 7b815f6ef232c..189ad671f01de 100644
--- a/llvm/test/tools/dsymutil/cmdline.test
+++ b/llvm/test/tools/dsymutil/cmdline.test
@@ -12,6 +12,7 @@ CHECK: -build-variant-suffix <suffix=buildvariant>
CHECK: -disallow <path>
CHECK: -dump-debug-map
CHECK: -D <path>
+CHECK: -embed-resource <src-path>=<bundle-relative-path>
CHECK: -fat64
CHECK: -flat
CHECK: -gen-reproducer
@@ -57,3 +58,6 @@ CONFLICT: error: --quiet and --verbose cannot be specified together
RUN: not dsymutil -y file1 --allow file2 2>&1 | FileCheck --check-prefix=CONFLICT2 %s
CONFLICT2: error: -y and --allow/--disallow cannot be specified together
+
+RUN: not dsymutil --flat --embed-resource /tmp/a.py=Python/a.py file1 2>&1 | FileCheck --check-prefix=FLATEMBED %s
+FLATEMBED: error: --embed-resource is not supported with --flat
diff --git a/llvm/test/tools/dsymutil/embed-resource.test b/llvm/test/tools/dsymutil/embed-resource.test
new file mode 100644
index 0000000000000..27b5ddfc21c92
--- /dev/null
+++ b/llvm/test/tools/dsymutil/embed-resource.test
@@ -0,0 +1,65 @@
+Test the --embed-resource option.
+
+Create a temp resource file and embed it into the dSYM bundle.
+
+RUN: rm -rf %t.dir && mkdir -p %t.dir
+RUN: echo "# test resource" > %t.dir/test.py
+RUN: dsymutil -oso-prepend-path=%p %p/Inputs/basic.macho.x86_64 \
+RUN: --embed-resource %t.dir/test.py=Python/test.py \
+RUN: -o %t.dir/basic.macho.x86_64.dSYM
+RUN: diff %t.dir/test.py %t.dir/basic.macho.x86_64.dSYM/Contents/Resources/Python/test.py
+
+Verify multiple --embed-resource options.
+
+RUN: rm -rf %t.dir/basic.macho.x86_64.dSYM
+RUN: echo "second" > %t.dir/second.txt
+RUN: dsymutil -oso-prepend-path=%p %p/Inputs/basic.macho.x86_64 \
+RUN: --embed-resource %t.dir/test.py=Python/test.py \
+RUN: --embed-resource %t.dir/second.txt=Scripts/second.txt \
+RUN: -o %t.dir/basic.macho.x86_64.dSYM
+RUN: diff %t.dir/test.py %t.dir/basic.macho.x86_64.dSYM/Contents/Resources/Python/test.py
+RUN: diff %t.dir/second.txt %t.dir/basic.macho.x86_64.dSYM/Contents/Resources/Scripts/second.txt
+
+Verify verbose output.
+
+RUN: rm -rf %t.dir/basic.macho.x86_64.dSYM
+RUN: dsymutil -oso-prepend-path=%p %p/Inputs/basic.macho.x86_64 \
+RUN: --embed-resource %t.dir/test.py=Python/test.py \
+RUN: --verbose -o %t.dir/basic.macho.x86_64.dSYM 2>&1 \
+RUN: | FileCheck --check-prefix=VERBOSE %s
+
+VERBOSE: embed resource {{.*}}test.py -> {{.*}}Resources{{.}}Python{{.}}test.py
+
+Verify error on missing source file.
+
+RUN: not dsymutil -oso-prepend-path=%p %p/Inputs/basic.macho.x86_64 \
+RUN: --embed-resource %t.dir/nonexistent.py=Python/foo.py \
+RUN: -o %t.dir/basic.macho.x86_64.dSYM 2>&1 \
+RUN: | FileCheck --check-prefix=MISSING %s
+
+MISSING: error: cannot embed resource {{.*}}nonexistent.py
+
+Verify directory embedding copies all files recursively.
+
+RUN: rm -rf %t.dir/basic.macho.x86_64.dSYM
+RUN: mkdir -p %t.dir/pydir/sub
+RUN: echo "file_a" > %t.dir/pydir/a.py
+RUN: echo "file_b" > %t.dir/pydir/b.py
+RUN: echo "file_sub" > %t.dir/pydir/sub/c.py
+RUN: dsymutil -oso-prepend-path=%p %p/Inputs/basic.macho.x86_64 \
+RUN: --embed-resource %t.dir/pydir=Python \
+RUN: -o %t.dir/basic.macho.x86_64.dSYM
+RUN: diff %t.dir/pydir/a.py %t.dir/basic.macho.x86_64.dSYM/Contents/Resources/Python/a.py
+RUN: diff %t.dir/pydir/b.py %t.dir/basic.macho.x86_64.dSYM/Contents/Resources/Python/b.py
+RUN: diff %t.dir/pydir/sub/c.py %t.dir/basic.macho.x86_64.dSYM/Contents/Resources/Python/sub/c.py
+
+Verify error cases.
+
+RUN: not dsymutil --embed-resource noequals file1 2>&1 | FileCheck --check-prefix=BADEMBED %s
+BADEMBED: error: invalid --embed-resource argument 'noequals': expected <src-path>=<dst-path>
+
+RUN: not dsymutil --embed-resource /tmp/a.py=../../etc/passwd file1 2>&1 | FileCheck --check-prefix=ESCAPE %s
+ESCAPE: error: invalid --embed-resource destination '../../etc/passwd': must be a relative path within the bundle
+
+RUN: not dsymutil --embed-resource /tmp/a.py=/etc/passwd file1 2>&1 | FileCheck --check-prefix=ABSOLUTE %s
+ABSOLUTE: error: invalid --embed-resource destination '/etc/passwd': must be a relative path within the bundle
diff --git a/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp b/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp
index 7114e1924daa7..d4f7c36dd6046 100644
--- a/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp
+++ b/llvm/tools/dsymutil/DwarfLinkerForBinary.cpp
@@ -533,6 +533,63 @@ Error DwarfLinkerForBinary::copySwiftInterfaces(StringRef Architecture) const {
return Error::success();
}
+Error DwarfLinkerForBinary::copyEmbeddedResources() const {
+ if (!Options.ResourceDir || Options.EmbedResources.empty())
+ return Error::success();
+
+ auto copyOneFile = [&](StringRef SrcPath,
+ StringRef DstPath) -> std::error_code {
+ if (auto EC = sys::fs::create_directories(sys::path::parent_path(DstPath),
+ true, sys::fs::perms::all_all))
+ return EC;
+
+ if (Options.Verbose)
+ outs() << "embed resource " << SrcPath << " -> " << DstPath << '\n';
+
+ return sys::fs::copy_file(SrcPath, DstPath);
+ };
+
+ for (const auto &[Src, Dst] : Options.EmbedResources) {
+ bool IsDir = false;
+ if (auto EC = sys::fs::is_directory(Src, IsDir))
+ return make_error<StringError>("cannot embed resource " + Src + ": " +
+ toString(errorCodeToError(EC)),
+ EC);
+
+ if (IsDir) {
+ std::error_code EC;
+ for (sys::fs::recursive_directory_iterator I(Src, EC), E; I != E && !EC;
+ I.increment(EC)) {
+ if (I->type() == sys::fs::file_type::directory_file)
+ continue;
+ StringRef FilePath = I->path();
+ StringRef Relative = FilePath.substr(StringRef(Src).size());
+ if (!Relative.empty() && sys::path::is_separator(Relative.front()))
+ Relative = Relative.drop_front();
+ SmallString<128> DestPath;
+ sys::path::append(DestPath, *Options.ResourceDir, Dst, Relative);
+ if (auto CopyEC = copyOneFile(FilePath, DestPath))
+ return make_error<StringError>("cannot embed resource " + FilePath +
+ ": " +
+ toString(errorCodeToError(CopyEC)),
+ CopyEC);
+ }
+ if (EC)
+ return make_error<StringError>("cannot read directory " + Src + ": " +
+ toString(errorCodeToError(EC)),
+ EC);
+ } else {
+ SmallString<128> DestPath;
+ sys::path::append(DestPath, *Options.ResourceDir, Dst);
+ if (auto EC = copyOneFile(Src, DestPath))
+ return make_error<StringError>("cannot embed resource " + Src + ": " +
+ toString(errorCodeToError(EC)),
+ EC);
+ }
+ }
+ return Error::success();
+}
+
void DwarfLinkerForBinary::copySwiftReflectionMetadata(
const llvm::dsymutil::DebugMapObject *Obj, classic::DwarfStreamer *Streamer,
std::vector<uint64_t> &SectionToOffsetInDwarf,
@@ -877,6 +934,9 @@ bool DwarfLinkerForBinary::linkImpl(
return error(toString(std::move(E)));
}
+ if (auto E = copyEmbeddedResources())
+ return error(toString(std::move(E)));
+
auto MapTriple = Map.getTriple();
if ((MapTriple.isOSDarwin() || MapTriple.isOSBinFormatMachO()) &&
!Map.getBinaryPath().empty() &&
diff --git a/llvm/tools/dsymutil/DwarfLinkerForBinary.h b/llvm/tools/dsymutil/DwarfLinkerForBinary.h
index d71ac0cd3eefd..507a523ba4530 100644
--- a/llvm/tools/dsymutil/DwarfLinkerForBinary.h
+++ b/llvm/tools/dsymutil/DwarfLinkerForBinary.h
@@ -275,6 +275,8 @@ class DwarfLinkerForBinary {
Error copySwiftInterfaces(StringRef Architecture) const;
+ Error copyEmbeddedResources() const;
+
void copySwiftReflectionMetadata(
const llvm::dsymutil::DebugMapObject *Obj,
classic::DwarfStreamer *Streamer,
diff --git a/llvm/tools/dsymutil/LinkUtils.h b/llvm/tools/dsymutil/LinkUtils.h
index 27f671152a114..9db5efd02852e 100644
--- a/llvm/tools/dsymutil/LinkUtils.h
+++ b/llvm/tools/dsymutil/LinkUtils.h
@@ -88,6 +88,9 @@ struct LinkOptions {
/// The Resources directory in the .dSYM bundle.
std::optional<std::string> ResourceDir;
+ /// Resources to embed in the dSYM bundle's Contents/Resources/ directory.
+ std::vector<std::pair<std::string, std::string>> EmbedResources;
+
/// Virtual File System.
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS =
vfs::getRealFileSystem();
diff --git a/llvm/tools/dsymutil/Options.td b/llvm/tools/dsymutil/Options.td
index 9b262660fe99a..520025ab957c5 100644
--- a/llvm/tools/dsymutil/Options.td
+++ b/llvm/tools/dsymutil/Options.td
@@ -229,6 +229,15 @@ def build_variant_suffix: Separate<["--", "-"], "build-variant-suffix">,
Group<grp_general>;
def: Joined<["--", "-"], "build-variant-suffix=">, Alias<build_variant_suffix>;
+def embed_resource: Separate<["--", "-"], "embed-resource">,
+ MetaVarName<"<src-path>=<bundle-relative-path>">,
+ HelpText<"Copy a file or directory into the dSYM bundle's Resources directory. "
+ "The argument is <source-path>=<destination-relative-to-Resources>. "
+ "If the source is a directory, its contents are copied recursively. "
+ "This option can be specified multiple times.">,
+ Group<grp_general>;
+def: Joined<["--", "-"], "embed-resource=">, Alias<embed_resource>;
+
def allow: Separate<["--", "-"], "allow">,
MetaVarName<"<path>">,
HelpText<"Only process debug map objects listed in the YAML file at <path>. "
diff --git a/llvm/tools/dsymutil/dsymutil.cpp b/llvm/tools/dsymutil/dsymutil.cpp
index 108b840ce1c68..e986304830213 100644
--- a/llvm/tools/dsymutil/dsymutil.cpp
+++ b/llvm/tools/dsymutil/dsymutil.cpp
@@ -216,6 +216,11 @@ static Error verifyOptions(const DsymutilOptions &Options) {
"--allow and --disallow cannot be specified together",
errc::invalid_argument);
+ if (Options.Flat && !Options.LinkOpts.EmbedResources.empty())
+ return make_error<StringError>(
+ "--embed-resource is not supported with --flat",
+ errc::invalid_argument);
+
return Error::success();
}
@@ -415,6 +420,28 @@ static Expected<DsymutilOptions> getOptions(opt::InputArgList &Args) {
if (opt::Arg *BuildVariantSuffix = Args.getLastArg(OPT_build_variant_suffix))
Options.LinkOpts.BuildVariantSuffix = BuildVariantSuffix->getValue();
+ for (auto *Arg : Args.filtered(OPT_embed_resource)) {
+ StringRef Val = Arg->getValue();
+ auto [Src, Dst] = Val.split('=');
+ if (Src.empty() || Dst.empty())
+ return make_error<StringError>("invalid --embed-resource argument '" +
+ Val +
+ "': expected <src-path>=<dst-path>",
+ inconvertibleErrorCode());
+
+ // Reject destinations that would escape the Resources directory.
+ SmallString<128> NormalizedDst(Dst);
+ sys::path::remove_dots(NormalizedDst, /*remove_dot_dot=*/true);
+ if (sys::path::is_absolute(NormalizedDst) ||
+ NormalizedDst.starts_with(".."))
+ return make_error<StringError>(
+ "invalid --embed-resource destination '" + Dst +
+ "': must be a relative path within the bundle",
+ inconvertibleErrorCode());
+ Options.LinkOpts.EmbedResources.emplace_back(Src.str(),
+ std::string(NormalizedDst));
+ }
+
for (auto *SearchPath : Args.filtered(OPT_dsym_search_path))
Options.LinkOpts.DSYMSearchPaths.push_back(SearchPath->getValue());
More information about the llvm-commits
mailing list