From 3bacedf966ff0d2b9ed5b4f4c1329a05dee4cbcd Mon Sep 17 00:00:00 2001 From: Rumou Duan Date: Tue, 13 Oct 2015 17:30:00 +0000 Subject: Fix an edge case in which we have imported libraries with duplicated base names. A shell script build phase is added to copy the imported libraries to BUILT_PRODUCT_DIR with unique names before linking. -- MOS_MIGRATED_REVID=105324431 --- .../build/xcode/xcodegen/XcodeprojGeneration.java | 72 ++++++++++++++++------ 1 file changed, 52 insertions(+), 20 deletions(-) (limited to 'src/objc_tools') diff --git a/src/objc_tools/xcodegen/java/com/google/devtools/build/xcode/xcodegen/XcodeprojGeneration.java b/src/objc_tools/xcodegen/java/com/google/devtools/build/xcode/xcodegen/XcodeprojGeneration.java index b0ed080222..011d323f45 100644 --- a/src/objc_tools/xcodegen/java/com/google/devtools/build/xcode/xcodegen/XcodeprojGeneration.java +++ b/src/objc_tools/xcodegen/java/com/google/devtools/build/xcode/xcodegen/XcodeprojGeneration.java @@ -54,6 +54,7 @@ import com.facebook.buck.apple.xcode.xcodeproj.PBXProject; import com.facebook.buck.apple.xcode.xcodeproj.PBXReference; import com.facebook.buck.apple.xcode.xcodeproj.PBXReference.SourceTree; import com.facebook.buck.apple.xcode.xcodeproj.PBXResourcesBuildPhase; +import com.facebook.buck.apple.xcode.xcodeproj.PBXShellScriptBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXSourcesBuildPhase; import com.facebook.buck.apple.xcode.xcodeproj.PBXTarget.ProductType; import com.facebook.buck.apple.xcode.xcodeproj.PBXTargetDependency; @@ -67,6 +68,7 @@ import java.nio.file.FileSystem; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; @@ -331,18 +333,6 @@ public class XcodeprojGeneration { return (NSArray) NSObject.wrap(result.build().asList()); } - /** - * Returns the {@code LIBRARY_SEARCH_PATHS} array for a target's imported static libraries. - */ - private static NSArray librarySearchPaths(Iterable importedLibraries) { - ImmutableSet.Builder result = new ImmutableSet.Builder<>(); - for (String importedLibrary : importedLibraries) { - result.add(new NSString("$(WORKSPACE_ROOT)/" + Paths.get(importedLibrary).getParent())); - } - - return (NSArray) NSObject.wrap(result.build().asList()); - } - /** * Returns the {@code ARCHS} array for a target's build config given the list of architecture * strings. If none is given, an array with default architectures "armv7" and "arm64" will be @@ -388,6 +378,25 @@ public class XcodeprojGeneration { return flags.build(); } + /** + * Returns a unique name for the given imported library path, scoped by both the base name and + * the parent directories. For example, with "foo/bar/lib.a", "lib_bar_foo.a" will be returned. + */ + private static String uniqueImportedLibraryName(String importedLibrary) { + String extension = ""; + String pathWithoutExtension = ""; + int i = importedLibrary.lastIndexOf('.'); + if (i > 0) { + extension = importedLibrary.substring(i); + pathWithoutExtension = importedLibrary.substring(0, i); + } else { + pathWithoutExtension = importedLibrary; + } + + String[] pathFragments = pathWithoutExtension.replace("-", "_").split("/"); + return Joiner.on("_").join(Lists.reverse(Arrays.asList(pathFragments))) + extension; + } + /** Generates a project file. */ public static PBXProject xcodeproj(Path workspaceRoot, Control control, Iterable postProcessors) { @@ -520,11 +529,6 @@ public class XcodeprojGeneration { targetBuildConfigMap.put(name, value); } - if (!Equaling.of(ProductType.STATIC_LIBRARY, productType(targetControl))) { - targetBuildConfigMap.put("LIBRARY_SEARCH_PATHS", - librarySearchPaths(targetControl.getImportedLibraryList())); - } - // Note that HFS+ (the Mac filesystem) is usually case insensitive, so we cast all target // names to lower case before checking for duplication because otherwise users may end up // having duplicated intermediate build directories that can interfere with the build. @@ -603,12 +607,40 @@ public class XcodeprojGeneration { targetInfo.addDependencyInfo(dependency, targetInfoByLabel); } - if (!Equaling.of(ProductType.STATIC_LIBRARY, productType(targetControl))) { + if (!Equaling.of(ProductType.STATIC_LIBRARY, productType(targetControl)) + && !targetControl.getImportedLibraryList().isEmpty()) { + // We add a script build phase to copy the imported libraries to BUILT_PRODUCT_DIR with + // unique names before linking them to work around an Xcode issue where imported libraries + // with duplicated names lead to link errors. + // + // Internally Xcode uses linker flag -l{LIBRARY_NAME} to link a particular library and + // delegates to the linker to locate the actual library using library search paths. So given + // two imported libraries with the same name: a/b/libfoo.a, c/d/libfoo.a, Xcode uses + // duplicate linker flag -lfoo to link both of the libraries. Depending on the order of + // the library search paths, the linker will only be able to locate and link one of the + // libraries. + // + // With this workaround using a script build phase, all imported libraries to link have + // unique names. For the previous example with a/b/libfoo.a and c/d/libfoo.a, the script + // build phase will copy them to BUILT_PRODUCTS_DIR with unique names libfoo_b_a.a and + // libfoo_d_c.a, respectively. The linker flags Xcode uses to link them will be + // -lfoo_d_c and -lfoo_b_a, with no duplication. + PBXShellScriptBuildPhase scriptBuildPhase = new PBXShellScriptBuildPhase(); + scriptBuildPhase.setShellScript( + "for ((i=0; i < ${SCRIPT_INPUT_FILE_COUNT}; i++)) do\n" + + " INPUT_FILE=\"SCRIPT_INPUT_FILE_${i}\"\n" + + " OUTPUT_FILE=\"SCRIPT_OUTPUT_FILE_${i}\"\n" + + " cp -v -f \"${!INPUT_FILE}\" \"${!OUTPUT_FILE}\"\n" + + "done"); for (String importedLibrary : targetControl.getImportedLibraryList()) { - FileReference fileReference = FileReference.of(importedLibrary, SourceTree.GROUP) - .withExplicitFileType(FILE_TYPE_ARCHIVE_LIBRARY); + String uniqueImportedLibrary = uniqueImportedLibraryName(importedLibrary); + scriptBuildPhase.getInputPaths().add("$(WORKSPACE_ROOT)/" + importedLibrary); + scriptBuildPhase.getOutputPaths().add("$(BUILT_PRODUCTS_DIR)/" + uniqueImportedLibrary); + FileReference fileReference = FileReference.of(uniqueImportedLibrary, + SourceTree.BUILT_PRODUCTS_DIR).withExplicitFileType(FILE_TYPE_ARCHIVE_LIBRARY); targetInfo.frameworksPhase.getFiles().add(pbxBuildFiles.getStandalone(fileReference)); } + targetInfo.nativeTarget.getBuildPhases().add(scriptBuildPhase); } } -- cgit v1.2.3