/* * Copyright 2013-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.facebook.buck.apple; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeTrue; import com.dd.plist.NSDictionary; import com.dd.plist.NSNumber; import com.dd.plist.NSString; import com.dd.plist.PropertyListParser; import com.facebook.buck.cxx.LinkerMapMode; import com.facebook.buck.cxx.StripStyle; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.BuildTargetFactory; import com.facebook.buck.model.BuildTargets; import com.facebook.buck.model.Flavor; import com.facebook.buck.model.InternalFlavor; import com.facebook.buck.testutil.TestConsole; import com.facebook.buck.testutil.integration.BuckBuildLog; import com.facebook.buck.testutil.integration.FakeAppleDeveloperEnvironment; import com.facebook.buck.testutil.integration.ProjectWorkspace; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.testutil.integration.TestDataHelper; import com.facebook.buck.util.DefaultProcessExecutor; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.ProcessExecutor; import com.facebook.buck.util.environment.Platform; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import java.io.IOException; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; import org.hamcrest.Matchers; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class AppleBundleIntegrationTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); @Rule public ExpectedException thrown = ExpectedException.none(); private ProjectFilesystem filesystem; @Before public void setUp() throws InterruptedException { filesystem = new ProjectFilesystem(tmp.getRoot()); } private boolean checkCodeSigning(Path absoluteBundlePath) throws IOException, InterruptedException { if (!Files.exists(absoluteBundlePath)) { throw new NoSuchFileException(absoluteBundlePath.toString()); } return CodeSigning.hasValidSignature( new DefaultProcessExecutor(new TestConsole()), absoluteBundlePath); } private void runSimpleApplicationBundleTestWithBuildTarget(String fqtn) throws IOException, InterruptedException { ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_no_debug", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget(fqtn); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, target.withAppendedFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR), "%s")); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, target.withAppendedFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR), "%s") .resolve(target.getShortName() + ".app")); assertTrue(Files.exists(appPath.resolve(target.getShortName()))); assertTrue(checkCodeSigning(appPath)); // Non-Swift target shouldn't include Frameworks/ assertFalse(Files.exists(appPath.resolve("Frameworks"))); } @Test public void testDisablingBundleCaching() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_no_debug", tmp); workspace.setUp(); final String target = "//:DemoApp#iphonesimulator-x86_64,no-debug,no-include-frameworks"; workspace.enableDirCache(); workspace.runBuckBuild("-c", "apple.cache_bundles_and_packages=false", target).assertSuccess(); workspace.runBuckCommand("clean"); workspace.runBuckBuild("-c", "apple.cache_bundles_and_packages=false", target).assertSuccess(); workspace.getBuildLog().assertTargetBuiltLocally(target); } @Test public void simpleApplicationBundle() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); runSimpleApplicationBundleTestWithBuildTarget("//:DemoApp#iphonesimulator-x86_64,no-debug"); } @Test public void simpleApplicationBundleWithLinkerMapDoesNotAffectOutput() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); runSimpleApplicationBundleTestWithBuildTarget("//:DemoApp#iphonesimulator-x86_64,no-debug"); } @Test public void simpleApplicationBundleWithoutLinkerMapDoesNotAffectOutput() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); runSimpleApplicationBundleTestWithBuildTarget( "//:DemoApp#iphonesimulator-x86_64,no-debug,no-linkermap"); } @Test public void simpleApplicationBundleWithCodeSigning() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(FakeAppleDeveloperEnvironment.supportsCodeSigning()); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_with_codesigning", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#iphoneos-arm64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); assertTrue(Files.exists(appPath.resolve(target.getShortName()))); assertTrue(checkCodeSigning(appPath)); // Do not match iOS profiles on tvOS targets. target = workspace.newBuildTarget("//:DemoApp#appletvos-arm64,no-debug"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", target.getFullyQualifiedName()); result.assertFailure(); assertTrue(result.getStderr().contains("No valid non-expired provisioning profiles match")); // Match tvOS profile. workspace.addBuckConfigLocalOption( "apple", "provisioning_profile_search_path", "provisioning_profiles_tvos"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); } @Test public void simpleApplicationBundleWithTargetCodeSigning() throws Exception { assertTargetCodesignToolIsUsedFor("//:DemoApp#iphoneos-arm64,no-debug"); } @Test public void simpleFatApplicationBundleWithTargetCodeSigning() throws Exception { assertTargetCodesignToolIsUsedFor("//:DemoApp#iphoneos-arm64,iphoneos-armv7,no-debug"); } private void assertTargetCodesignToolIsUsedFor(String fullyQualifiedName) throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(FakeAppleDeveloperEnvironment.supportsCodeSigning()); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_with_target_codesigning", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget(fullyQualifiedName); ProjectWorkspace.ProcessResult buildResult = workspace.runBuckCommand("build", target.getFullyQualifiedName()); // custom codesign tool exits with non-zero error code and prints a message to the stderr, so // that its use can be detected assertThat(buildResult.getStderr(), containsString("codesign was here")); } private NSDictionary verifyAndParsePlist(Path path) throws Exception { assertTrue(Files.exists(path)); String resultContents = filesystem.readFileIfItExists(path).get(); NSDictionary resultPlist = (NSDictionary) PropertyListParser.parse(resultContents.getBytes(Charsets.UTF_8)); return resultPlist; } @Test public void simpleApplicationBundleWithDryRunCodeSigning() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(FakeAppleDeveloperEnvironment.supportsCodeSigning()); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_with_codesigning", tmp); workspace.setUp(); workspace.addBuckConfigLocalOption("apple", "dry_run_code_signing", "true"); BuildTarget target = workspace.newBuildTarget("//:DemoAppWithFramework#iphoneos-arm64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); Path codeSignResultsPath = appPath.resolve("BUCK_code_sign_entitlements.plist"); assertTrue(Files.exists(codeSignResultsPath)); NSDictionary resultPlist = verifyAndParsePlist(appPath.resolve("BUCK_pp_dry_run.plist")); assertEquals(new NSString("com.example.DemoApp"), resultPlist.get("bundle-id")); assertEquals(new NSString("12345ABCDE"), resultPlist.get("team-identifier")); assertEquals( new NSString("00000000-0000-0000-0000-000000000000"), resultPlist.get("provisioning-profile-uuid")); // Codesigning main bundle resultPlist = verifyAndParsePlist(appPath.resolve("BUCK_code_sign_args.plist")); assertEquals(new NSNumber(true), resultPlist.get("use-entitlements")); // Codesigning embedded framework bundle resultPlist = verifyAndParsePlist( appPath.resolve("Frameworks/DemoFramework.framework/BUCK_code_sign_args.plist")); assertEquals(new NSNumber(false), resultPlist.get("use-entitlements")); } @Test public void simpleApplicationBundleWithEmbeddedFrameworks() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(FakeAppleDeveloperEnvironment.supportsCodeSigning()); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_with_codesigning", tmp); workspace.setUp(); BuildTarget appTarget = workspace.newBuildTarget( "//:DemoAppWithFramework#iphoneos-arm64,no-debug,include-frameworks"); workspace.runBuckCommand("build", appTarget.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoAppWithFramework_output.expected"), BuildTargets.getGenPath(filesystem, appTarget, "%s")); Path appPath = workspace.getPath( BuildTargets.getGenPath(filesystem, appTarget, "%s") .resolve(appTarget.getShortName() + ".app")); assertTrue(Files.exists(appPath.resolve(appTarget.getShortName()))); assertTrue(checkCodeSigning(appPath)); BuildTarget frameworkTarget = workspace.newBuildTarget("//:DemoFramework#iphoneos-arm64,no-debug,no-include-frameworks"); Path frameworkPath = workspace.getPath( BuildTargets.getGenPath(filesystem, frameworkTarget, "%s") .resolve(frameworkTarget.getShortName() + ".framework")); assertFalse(checkCodeSigning(frameworkPath)); Path embeddedFrameworkPath = appPath.resolve(Paths.get("Frameworks/DemoFramework.framework")); assertTrue(Files.exists(embeddedFrameworkPath.resolve(frameworkTarget.getShortName()))); assertTrue(checkCodeSigning(embeddedFrameworkPath)); } @Test public void simpleApplicationBundleWithCodeSigningAndEntitlements() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(FakeAppleDeveloperEnvironment.supportsCodeSigning()); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_with_codesigning_and_entitlements", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#iphoneos-arm64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); workspace.assertFilesEqual( Paths.get("DemoApp.xcent.expected"), BuildTargets.getScratchPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s.xcent")); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); assertTrue(Files.exists(appPath.resolve(target.getShortName()))); assertTrue(checkCodeSigning(appPath)); } @Test public void simpleApplicationBundleWithFatBinary() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_fat_application_bundle_no_debug", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#iphonesimulator-i386,iphonesimulator-x86_64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); Path outputFile = appPath.resolve(target.getShortName()); assertTrue(Files.exists(outputFile)); ProcessExecutor.Result result = workspace.runCommand("lipo", outputFile.toString(), "-verify_arch", "i386", "x86_64"); assertEquals(0, result.getExitCode()); } @Test public void bundleHasOutputPath() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_no_debug", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#no-debug"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("targets", "--show-output", target.getFullyQualifiedName()); result.assertSuccess(); Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertEquals( String.format("%s %s", target.getFullyQualifiedName(), appPath), result.getStdout().trim()); } @Test public void extensionBundle() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "simple_extension", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoExtension#no-debug"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("targets", "--show-output", target.getFullyQualifiedName()); result.assertSuccess(); Path extensionPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".appex"); assertEquals( String.format("%s %s", target.getFullyQualifiedName(), extensionPath), result.getStdout().trim()); result = workspace.runBuckCommand("build", target.getFullyQualifiedName()); result.assertSuccess(); Path outputBinary = workspace.getPath(extensionPath.resolve(target.getShortName())); assertTrue( String.format( "Extension binary could not be found inside the appex dir [%s].", outputBinary), Files.exists(outputBinary)); } @Test public void appBundleWithExtensionBundleDependency() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "simple_app_with_extension", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoAppWithExtension#no-debug"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("targets", "--show-output", target.getFullyQualifiedName()); result.assertSuccess(); Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertEquals( String.format("%s %s", target.getFullyQualifiedName(), appPath), result.getStdout().trim()); result = workspace.runBuckCommand("build", target.getFullyQualifiedName()); result.assertSuccess(); assertTrue(Files.exists(workspace.getPath(appPath.resolve("DemoAppWithExtension")))); assertTrue( Files.exists( workspace.getPath(appPath.resolve("PlugIns/DemoExtension.appex/DemoExtension")))); } @Test public void bundleBinaryHasDsymBundle() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_dwarf_and_dsym", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#dwarf-and-dsym,iphonesimulator-x86_64"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path bundlePath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); Path dwarfPath = bundlePath.getParent().resolve("DemoApp.app.dSYM/Contents/Resources/DWARF/DemoApp"); Path binaryPath = bundlePath.resolve("DemoApp"); assertTrue(Files.exists(dwarfPath)); AppleDsymTestUtil.checkDsymFileHasDebugSymbolForMain(workspace, dwarfPath); ProcessExecutor.Result result = workspace.runCommand( "dsymutil", "-o", binaryPath.toString() + ".test.dSYM", binaryPath.toString()); String dsymutilOutput = ""; if (result.getStderr().isPresent()) { dsymutilOutput = result.getStderr().get(); } if (dsymutilOutput.isEmpty()) { assertThat(result.getStdout().isPresent(), is(true)); dsymutilOutput = result.getStdout().get(); } assertThat(dsymutilOutput, containsString("warning: no debug symbols in executable")); } @Test public void bundleBinaryHasLinkerMapFile() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_dwarf_and_dsym", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#iphonesimulator-x86_64"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, target .withAppendedFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .withAppendedFlavors(AppleDebugFormat.DWARF_AND_DSYM.getFlavor()), "%s")); BuildTarget binaryWithLinkerMap = workspace.newBuildTarget("//:DemoAppBinary#iphonesimulator-x86_64"); Path binaryWithLinkerMapPath = BuildTargets.getGenPath(filesystem, binaryWithLinkerMap, "%s"); Path linkMapPath = BuildTargets.getGenPath(filesystem, binaryWithLinkerMap, "%s-LinkMap.txt"); assertThat(Files.exists(workspace.resolve(binaryWithLinkerMapPath)), Matchers.equalTo(true)); assertThat(Files.exists(workspace.resolve(linkMapPath)), Matchers.equalTo(true)); BuildTarget binaryWithoutLinkerMap = workspace .newBuildTarget("//:DemoAppBinary#iphonesimulator-x86_64") .withAppendedFlavors(LinkerMapMode.NO_LINKER_MAP.getFlavor()); Path binaryWithoutLinkerMapPath = BuildTargets.getGenPath(filesystem, binaryWithoutLinkerMap, "%s"); assertThat( Files.exists(workspace.resolve(binaryWithoutLinkerMapPath)), Matchers.equalTo(false)); } public String runSimpleBuildWithDefinedStripStyle(StripStyle stripStyle) throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_no_debug", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget( "//:DemoApp#iphonesimulator-x86_64," + stripStyle.getFlavor().getName()); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .addFlavors(stripStyle.getFlavor()) .addFlavors(AppleDebugFormat.NONE.getFlavor()) .build(), "%s")); Path bundlePath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .addFlavors(stripStyle.getFlavor()) .addFlavors(AppleDebugFormat.NONE.getFlavor()) .build(), "%s") .resolve(target.getShortName() + ".app")); Path binaryPath = bundlePath.resolve("DemoApp"); ProcessExecutor.Result result = workspace.runCommand("nm", binaryPath.toString()); return result.getStdout().orElse(""); } @Test public void bundleBinaryWithStripStyleAllDoesNotContainAnyDebugInfo() throws Exception { String nmOutput = runSimpleBuildWithDefinedStripStyle(StripStyle.ALL_SYMBOLS); assertThat(nmOutput, not(containsString("t -[AppDelegate window]"))); assertThat(nmOutput, not(containsString("S _OBJC_METACLASS_$_AppDelegate"))); } @Test public void bundleBinaryWithStripStyleNonGlobalContainsOnlyGlobals() throws Exception { String nmOutput = runSimpleBuildWithDefinedStripStyle(StripStyle.NON_GLOBAL_SYMBOLS); assertThat(nmOutput, not(containsString("t -[AppDelegate window]"))); assertThat(nmOutput, containsString("S _OBJC_METACLASS_$_AppDelegate")); } @Test public void bundleBinaryWithStripStyleDebuggingContainsGlobalsAndLocals() throws Exception { String nmOutput = runSimpleBuildWithDefinedStripStyle(StripStyle.DEBUGGING_SYMBOLS); assertThat(nmOutput, containsString("t -[AppDelegate window]")); assertThat(nmOutput, containsString("S _OBJC_METACLASS_$_AppDelegate")); } @Test public void appBundleWithResources() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "app_bundle_with_resources", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#iphonesimulator-x86_64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); } @Test public void appBundleVariantDirectoryMustEndInLproj() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); thrown.expect(HumanReadableException.class); thrown.expectMessage( Matchers.matchesPattern( "Variant files have to be in a directory with name ending in '\\.lproj', " + "but '.*/cc/Localizable.strings' is not.")); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "app_bundle_with_invalid_variant", tmp); workspace.setUp(); workspace.runBuckCommand("build", "//:DemoApp#iphonesimulator-x86_64,no-debug").assertFailure(); } @Test public void defaultPlatformInBuckConfig() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "default_platform_in_buckconfig_app_bundle", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDebugFormat.DWARF_AND_DSYM.getFlavor()) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDebugFormat.DWARF_AND_DSYM.getFlavor()) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertTrue(Files.exists(workspace.getPath(appPath.resolve(target.getShortName())))); } @Test public void defaultPlatformInBuckConfigWithFlavorSpecified() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "default_platform_in_buckconfig_flavored_app_bundle", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#iphonesimulator-x86_64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertTrue(Files.exists(workspace.getPath(appPath.resolve(target.getShortName())))); } @Test public void appleAssetCatalogsAreIncludedInBundle() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "apple_asset_catalogs_are_included_in_bundle", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); Path outputPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s"); workspace.verify(Paths.get("DemoApp_output.expected"), outputPath); Path appPath = outputPath.resolve(target.getShortName() + ".app"); assertTrue(Files.exists(workspace.getPath(appPath.resolve("Assets.car")))); } @Test public void appleAssetCatalogsWithMoreThanOneAppIconOrLaunchImageShouldFail() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); thrown.expect(HumanReadableException.class); thrown.expectMessage("At most one asset catalog in the dependencies of"); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "apple_asset_catalogs_are_included_in_bundle", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoAppWithMoreThanOneIconAndLaunchImage#no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()); } @Test public void appleBundleDoesNotPropagateIncludeFrameworkFlavors() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "simple_app_with_extension", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoAppWithExtension#no-debug"); ProjectWorkspace.ProcessResult result = workspace.runBuckCommand("build", "--show-output", target.getFullyQualifiedName()); result.assertSuccess(); BuckBuildLog buckBuildLog = workspace.getBuildLog(); ImmutableSet<String> targetsThatShouldContainIncludeFrameworkFlavors = ImmutableSet.of("//:DemoAppWithExtension", "//:DemoExtension"); ImmutableSet<Flavor> includeFrameworkFlavors = ImmutableSet.of( InternalFlavor.of("no-include-frameworks"), InternalFlavor.of("include-frameworks")); for (BuildTarget builtTarget : buckBuildLog.getAllTargets()) { if (Sets.intersection(builtTarget.getFlavors(), includeFrameworkFlavors).isEmpty()) { assertThat( builtTarget.getUnflavoredBuildTarget().getFullyQualifiedName(), not(in(targetsThatShouldContainIncludeFrameworkFlavors))); } else { assertThat( builtTarget.getUnflavoredBuildTarget().getFullyQualifiedName(), in(targetsThatShouldContainIncludeFrameworkFlavors)); } } } @Test public void infoPlistSubstitutionsAreApplied() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "application_bundle_with_substitutions", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#iphonesimulator-x86_64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertTrue(Files.exists(workspace.getPath(appPath.resolve(target.getShortName())))); NSDictionary plist = (NSDictionary) PropertyListParser.parse( Files.readAllBytes(workspace.getPath(appPath.resolve("Info.plist")))); assertThat( "Should contain xcode build version", (String) plist.get("DTXcodeBuild").toJavaObject(), not(emptyString())); } @Test public void infoPlistSubstitutionsAreAppliedToEntitlements() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(FakeAppleDeveloperEnvironment.supportsCodeSigning()); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "application_bundle_with_entitlements_substitutions", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#iphoneos-arm64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); workspace.assertFilesEqual( Paths.get("DemoApp.xcent.expected"), BuildTargets.getScratchPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s.xcent")); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); assertTrue(Files.exists(appPath.resolve(target.getShortName()))); assertTrue(checkCodeSigning(appPath)); } @Test public void productNameChangesBundleAndBinaryNames() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "application_bundle_with_product_name", tmp); workspace.setUp(); workspace.runBuckCommand("build", "//:DemoApp#iphonesimulator-x86_64,no-debug").assertSuccess(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#iphonesimulator-x86_64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify(); String productName = "BrandNewProduct"; Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(productName + ".app"); assertTrue(Files.exists(workspace.getPath(appPath.resolve(productName)))); } @Test public void infoPlistWithUnrecognizedVariableFails() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "application_bundle_with_invalid_substitutions", tmp); workspace.setUp(); workspace.runBuckCommand("build", "//:DemoApp#iphonesimulator-x86_64,no-debug").assertFailure(); } @Test public void resourcesAreCompiled() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "app_bundle_with_compiled_resources", tmp); workspace.setUp(); BuildTarget target = workspace.newBuildTarget("//:DemoApp#iphonesimulator-x86_64,no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertTrue(Files.exists(workspace.getPath(appPath.resolve("AppViewController.nib")))); assertTrue(Files.exists(workspace.getPath(appPath.resolve("Model.momd")))); assertTrue(Files.exists(workspace.getPath(appPath.resolve("Model2.momd")))); assertTrue(Files.exists(workspace.getPath(appPath.resolve("DemoApp.scnassets")))); } @Test public void watchApplicationBundle() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(AppleNativeIntegrationTestUtils.isApplePlatformAvailable(ApplePlatform.WATCHOS)); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario(this, "watch_application_bundle", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDebugFormat.NONE.getFlavor()) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); Path watchAppPath = appPath.resolve("Watch/DemoWatchApp.app"); assertTrue(Files.exists(watchAppPath.resolve("DemoWatchApp"))); assertTrue( Files.exists( watchAppPath.resolve("PlugIns/DemoWatchAppExtension.appex/DemoWatchAppExtension"))); assertTrue(Files.exists(watchAppPath.resolve("Interface.plist"))); } @Test public void legacyWatchApplicationBundle() throws IOException, InterruptedException { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue(AppleNativeIntegrationTestUtils.isApplePlatformAvailable(ApplePlatform.WATCHOS)); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "legacy_watch_application_bundle", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance( "//:DemoApp#no-debug,iphonesimulator-x86_64,iphonesimulator-i386"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); workspace.verify( Paths.get("DemoApp_output.expected"), BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s")); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDebugFormat.NONE.getFlavor()) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); Path watchExtensionPath = appPath.resolve("Plugins/DemoWatchAppExtension.appex"); assertTrue(Files.exists(watchExtensionPath.resolve("DemoWatchAppExtension"))); assertTrue(Files.exists(watchExtensionPath.resolve("DemoWatchApp.app/DemoWatchApp"))); assertTrue(Files.exists(watchExtensionPath.resolve("DemoWatchApp.app/_WatchKitStub/WK"))); assertTrue(Files.exists(watchExtensionPath.resolve("DemoWatchApp.app/Interface.plist"))); } @Test public void copiesFrameworkBundleIntoFrameworkDirectory() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue( AppleNativeIntegrationTestUtils.isApplePlatformAvailable(ApplePlatform.IPHONESIMULATOR)); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "app_bundle_with_embedded_framework", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDebugFormat.NONE.getFlavor()) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); Path frameworkPath = appPath.resolve("Frameworks/TestFramework.framework"); assertTrue(Files.exists(frameworkPath.resolve("TestFramework"))); } @Test public void onlyIncludesResourcesInBundlesWhichStaticallyLinkThem() throws Exception { assumeTrue(Platform.detect() == Platform.MACOS); assumeTrue( AppleNativeIntegrationTestUtils.isApplePlatformAvailable(ApplePlatform.IPHONESIMULATOR)); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "app_bundle_with_embedded_framework_and_resources", tmp); workspace.setUp(); BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#no-debug"); workspace.runBuckCommand("build", target.getFullyQualifiedName()).assertSuccess(); Path appPath = workspace.getPath( BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDebugFormat.NONE.getFlavor()) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app")); String resourceName = "Resource.plist"; assertFalse(Files.exists(appPath.resolve(resourceName))); Path frameworkPath = appPath.resolve("Frameworks/TestFramework.framework"); assertTrue(Files.exists(frameworkPath.resolve(resourceName))); } @Test public void testTargetOutputForAppleBundle() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "simple_application_bundle_no_debug", tmp); workspace.setUp(); ProjectWorkspace.ProcessResult result; // test no-debug output BuildTarget target = BuildTargetFactory.newInstance("//:DemoApp#no-debug"); result = workspace.runBuckCommand("targets", "--show-output", target.getFullyQualifiedName()); result.assertSuccess(); Path appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertThat( result.getStdout(), Matchers.startsWith(target.getFullyQualifiedName() + " " + appPath.toString())); // test debug output target = BuildTargetFactory.newInstance("//:DemoApp#dwarf-and-dsym"); result = workspace.runBuckCommand("targets", "--show-output", target.getFullyQualifiedName()); result.assertSuccess(); appPath = BuildTargets.getGenPath( filesystem, BuildTarget.builder(target) .addFlavors(AppleDescriptions.NO_INCLUDE_FRAMEWORKS_FLAVOR) .build(), "%s") .resolve(target.getShortName() + ".app"); assertThat( result.getStdout(), Matchers.startsWith(target.getFullyQualifiedName() + " " + appPath.toString())); } @Test public void resourcesFromOtherCellsCanBeProperlyIncluded() throws IOException { assumeTrue(Platform.detect() == Platform.MACOS); ProjectWorkspace workspace = TestDataHelper.createProjectWorkspaceForScenario( this, "bundle_with_resources_from_other_cells", tmp); workspace.setUp(); Path outputPath = workspace.buildAndReturnOutput("//:bundle#iphonesimulator-x86_64"); assertTrue("Resource file should exist.", Files.isRegularFile(outputPath.resolve("file.txt"))); } }