[lipo] add -remove flag (#188275)

Add the -remove flag to llvm-lipo. This matches the existing Darwin lipo
tool:
```
% xcrun lipo 2>&1 | grep remove
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/lipo: one of -create, -thin <arch_type>, -extract <arch_type>, -remove <arch_type>, -replace <arch_type> <file_name>, -verify_arch <arch_type> ... , -archs, -info, or -detailed_info must be specified
    -remove <arch_type> [-remove <arch_type> ...]
```
Assisted-by: claude-code
This commit is contained in:
Richard Howell
2026-03-24 13:10:43 -07:00
committed by GitHub
parent 0692bd52cb
commit cd1a598d86
3 changed files with 117 additions and 4 deletions

View File

@@ -0,0 +1,39 @@
# RUN: yaml2obj %p/Inputs/i386-x86_64-universal.yaml -o %t-universal.o
# RUN: not llvm-lipo %t-universal.o -remove arm64_32 -output /dev/null 2>&1 | FileCheck --check-prefix=ARCH_NOT_IN_FILE %s
# ARCH_NOT_IN_FILE: does not contain the specified architecture arm64_32 to remove
# RUN: yaml2obj %p/Inputs/i386-slice.yaml -o %t-i386.o
# RUN: not llvm-lipo %t-i386.o -remove i386 -output /dev/null 2>&1 | FileCheck --check-prefix=INPUT_NOT_A_FAT_FILE %s
# INPUT_NOT_A_FAT_FILE: must be a fat file when the -remove option is specified
# RUN: not llvm-lipo -remove FakeArch %t-universal.o -output /dev/null 2>&1 | FileCheck --check-prefix=INVALID_ARCH %s
# INVALID_ARCH: Invalid architecture: FakeArch
# RUN: yaml2obj %p/Inputs/i386-x86_64-armv7-arm64-universal.yaml -o %t-4arch.o
# RUN: llvm-lipo -remove i386 %t-4arch.o -output %t-removed.o
# RUN: llvm-lipo -info %t-removed.o | FileCheck --check-prefix=REMOVE_ONE %s
# REMOVE_ONE: x86_64 armv7 arm64
# RUN: llvm-lipo -remove i386 %t-universal.o -output %t-only-x86_64.o
# RUN: llvm-lipo -thin x86_64 %t-universal.o -output %t-thinned-x86_64.o
# RUN: yaml2obj %p/Inputs/x86_64-slice.yaml -o %t-x86_64.o
# RUN: cmp %t-thinned-x86_64.o %t-x86_64.o
# Multiple -remove flags
# RUN: llvm-lipo -remove i386 -remove armv7 %t-4arch.o -output %t-removed-multi.o
# RUN: llvm-lipo -info %t-removed-multi.o | FileCheck --check-prefix=REMOVE_MULTI %s
# REMOVE_MULTI: x86_64 arm64
# Multiple -remove flags removing all but one arch
# RUN: llvm-lipo -remove i386 -remove x86_64 -remove armv7 %t-4arch.o -output %t-removed-three.o
# RUN: llvm-lipo -info %t-removed-three.o | FileCheck --check-prefix=REMOVE_THREE %s
# REMOVE_THREE: arm64
# Error: one of multiple -remove arches not in file
# RUN: not llvm-lipo -remove i386 -remove arm64_32 %t-4arch.o -output /dev/null 2>&1 | FileCheck --check-prefix=MULTI_ARCH_NOT_IN_FILE %s
# MULTI_ARCH_NOT_IN_FILE: does not contain the specified architecture arm64_32 to remove
# Error: removing all arches
# RUN: not llvm-lipo -remove i386 -remove x86_64 %t-universal.o -output /dev/null 2>&1 | FileCheck --check-prefix=REMOVE_ALL %s
# REMOVE_ALL: removing all architectures would result in an empty universal binary

View File

@@ -44,6 +44,11 @@ def extract : Option<["-", "--"], "extract", KIND_SEPARATE>,
HelpText<"Create a universal output file containing only the specified "
"arch_type from the fat input file. Requires -output option">;
def remove : Option<["-", "--"], "remove", KIND_SEPARATE>,
Group<action_group>,
HelpText<"Remove the specified arch_type from the universal input "
"file. Requires -output option">;
def create : Option<["-", "--"], "create", KIND_FLAG>,
Group<action_group>,
HelpText<"Create a universal binary output file from the input "

View File

@@ -99,6 +99,7 @@ enum class LipoAction {
VerifyArch,
ThinArch,
ExtractArch,
RemoveArch,
CreateUniversal,
ReplaceArch,
};
@@ -112,6 +113,7 @@ struct Config {
SmallVector<InputFile, 1> InputFiles;
SmallVector<std::string, 1> VerifyArchList;
SmallVector<InputFile, 1> ReplacementFiles;
SmallVector<std::string, 1> RemoveArchList;
StringMap<const uint32_t> SegmentAlignments;
std::string ArchType;
std::string OutputFile;
@@ -229,14 +231,18 @@ static Config parseLipoOptions(ArrayRef<const char *> ArgsArr) {
SmallVector<opt::Arg *, 1> ActionArgs(InputArgs.filtered(LIPO_action_group));
if (ActionArgs.empty())
reportError("at least one action should be specified");
// errors if multiple actions specified other than replace
// multiple replace flags may be specified, as long as they are not mixed with
// other action flags
// errors if multiple actions specified other than replace or remove
// multiple replace/remove flags may be specified, as long as they are not
// mixed with other action flags
auto ReplacementArgsRange = InputArgs.filtered(LIPO_replace);
auto RemoveArgsRange = InputArgs.filtered(LIPO_remove);
if (ActionArgs.size() > 1 &&
ActionArgs.size() !=
static_cast<size_t>(std::distance(ReplacementArgsRange.begin(),
ReplacementArgsRange.end()))) {
ReplacementArgsRange.end())) &&
ActionArgs.size() !=
static_cast<size_t>(
std::distance(RemoveArgsRange.begin(), RemoveArgsRange.end()))) {
std::string Buf;
raw_string_ostream OS(Buf);
OS << "only one of the following actions can be specified:";
@@ -287,6 +293,19 @@ static Config parseLipoOptions(ArrayRef<const char *> ArgsArr) {
C.ActionToPerform = LipoAction::ExtractArch;
return C;
case LIPO_remove:
for (auto *Action : ActionArgs) {
std::string ArchType = Action->getValue();
validateArchitectureName(ArchType);
C.RemoveArchList.push_back(ArchType);
}
if (C.InputFiles.size() > 1)
reportError("remove expects a single input file");
if (C.OutputFile.empty())
reportError("remove expects a single output file");
C.ActionToPerform = LipoAction::RemoveArch;
return C;
case LIPO_create:
if (C.OutputFile.empty())
reportError("create expects a single output file to be specified");
@@ -672,6 +691,52 @@ extractSlice(LLVMContext &LLVMCtx, ArrayRef<OwningBinary<Binary>> InputBinaries,
exit(EXIT_SUCCESS);
}
[[noreturn]] static void
removeSlice(LLVMContext &LLVMCtx, ArrayRef<OwningBinary<Binary>> InputBinaries,
const StringMap<const uint32_t> &Alignments,
ArrayRef<std::string> ArchTypes, StringRef OutputFileName) {
assert(!ArchTypes.empty() &&
"The architecture type list should be non-empty");
assert(InputBinaries.size() == 1 && "Incorrect number of input binaries");
assert(!OutputFileName.empty() && "Remove expects a single output file");
if (InputBinaries.front().getBinary()->isMachO()) {
reportError("input file " +
InputBinaries.front().getBinary()->getFileName() +
" must be a fat file when the -remove option is specified");
}
SmallVector<std::unique_ptr<SymbolicFile>, 2> ExtractedObjects;
SmallVector<std::unique_ptr<Archive>, 2> ExtractedArchives;
SmallVector<Slice, 2> Slices = buildSlices(
LLVMCtx, InputBinaries, Alignments, ExtractedObjects, ExtractedArchives);
SmallVector<StringRef, 1> NotFound;
for (StringRef ArchType : ArchTypes) {
size_t SizeBefore = Slices.size();
erase_if(Slices, [ArchType](const Slice &S) {
return ArchType == S.getArchString();
});
if (Slices.size() == SizeBefore)
NotFound.push_back(ArchType);
}
if (!NotFound.empty())
reportError("fat input file " +
InputBinaries.front().getBinary()->getFileName() +
" does not contain the specified architecture " + NotFound[0] +
" to remove");
if (Slices.empty())
reportError(
"removing all architectures would result in an empty universal binary");
llvm::stable_sort(Slices);
if (Error E = writeUniversalBinary(Slices, OutputFileName))
reportError(std::move(E));
exit(EXIT_SUCCESS);
}
static StringMap<Slice>
buildReplacementSlices(ArrayRef<OwningBinary<Binary>> ReplacementBinaries,
const StringMap<const uint32_t> &Alignments) {
@@ -770,6 +835,10 @@ int llvm_lipo_main(int argc, char **argv, const llvm::ToolContext &) {
extractSlice(LLVMCtx, InputBinaries, C.SegmentAlignments, C.ArchType,
C.OutputFile);
break;
case LipoAction::RemoveArch:
removeSlice(LLVMCtx, InputBinaries, C.SegmentAlignments, C.RemoveArchList,
C.OutputFile);
break;
case LipoAction::CreateUniversal:
createUniversalBinary(
LLVMCtx, InputBinaries, C.SegmentAlignments, C.OutputFile,