diff --git a/src/com/facebook/buck/features/apple/project/NewNativeTargetProjectMutator.java b/src/com/facebook/buck/features/apple/project/NewNativeTargetProjectMutator.java index 1d7c7df1d9e..acfa80a6b04 100644 --- a/src/com/facebook/buck/features/apple/project/NewNativeTargetProjectMutator.java +++ b/src/com/facebook/buck/features/apple/project/NewNativeTargetProjectMutator.java @@ -353,8 +353,15 @@ private void addPhasesAndGroupsForSources(PBXNativeTarget target, PBXGroup targetGroup, ProjectFilesystem fileSystem) { String sourcesDir = "Sources"; + String mainTargetName = targetGroup.getName(); + if (productType == ProductTypes.UNIT_TEST + || productType == ProductTypes.UI_TEST) { + sourcesDir = "Tests"; + mainTargetName = mainTargetName.replaceAll("Tests$", ""); + } + PBXGroup sourcesGroup = targetGroup.getOrCreateChildGroupByName(sourcesDir); - Path originalPath = Paths.get(this.originalPath.toString(), targetGroup.getName(), sourcesDir); + Path originalPath = Paths.get(this.originalPath.toString(), mainTargetName, sourcesDir); if (fileSystem.exists(originalPath)) { Path relativePath = pathRelativizer.outputDirToRootRelative(originalPath); diff --git a/src/com/facebook/buck/features/apple/project/ProjectGenerator.java b/src/com/facebook/buck/features/apple/project/ProjectGenerator.java index 6bb51c5b932..33f7f9ebc48 100644 --- a/src/com/facebook/buck/features/apple/project/ProjectGenerator.java +++ b/src/com/facebook/buck/features/apple/project/ProjectGenerator.java @@ -1633,7 +1633,7 @@ && shouldEmbedSwiftRuntimeInBundleTarget(bundle) extraSettingsBuilder .put("TARGET_NAME", buildTargetName) .put("SRCROOT", srcRoot); - if ((productType == ProductTypes.UI_TEST || productType == ProductTypes.UNIT_TEST) && isFocusedOnTarget) { + if (productType == ProductTypes.UI_TEST && isFocusedOnTarget) { if (bundleLoaderNode.isPresent()) { BuildTarget testTarget = bundleLoaderNode.get().getBuildTarget(); extraSettingsBuilder.put("TEST_TARGET_NAME", getXcodeTargetName(testTarget)); diff --git a/src/com/facebook/buck/features/apple/project/ProjectGeneratorOptions.java b/src/com/facebook/buck/features/apple/project/ProjectGeneratorOptions.java index 9dd9b129e52..e92d5bb00b9 100644 --- a/src/com/facebook/buck/features/apple/project/ProjectGeneratorOptions.java +++ b/src/com/facebook/buck/features/apple/project/ProjectGeneratorOptions.java @@ -40,6 +40,12 @@ default boolean shouldGenerateProjectSchemes() { return false; } + /** Create schemes for each target's contained build and test targets. */ + @Value.Default + default boolean shouldGenerateTargetSchemes() { + return false; + } + /** Generate read-only project files */ @Value.Default default boolean shouldGenerateReadOnlyFiles() { diff --git a/src/com/facebook/buck/features/apple/project/WorkspaceAndProjectGenerator.java b/src/com/facebook/buck/features/apple/project/WorkspaceAndProjectGenerator.java index 315d2f0db49..2686319abc7 100644 --- a/src/com/facebook/buck/features/apple/project/WorkspaceAndProjectGenerator.java +++ b/src/com/facebook/buck/features/apple/project/WorkspaceAndProjectGenerator.java @@ -68,6 +68,7 @@ import com.google.common.collect.ImmutableSetMultimap; import com.google.common.collect.ImmutableSortedMap; import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.ImmutableCollection; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; @@ -91,6 +92,7 @@ import java.util.stream.Stream; import com.facebook.buck.apple.AppleLibraryDescription; import com.facebook.buck.apple.AppleLibraryDescriptionArg; +import com.facebook.buck.apple.AppleTestDescription; public class WorkspaceAndProjectGenerator { private static final Logger LOG = Logger.get(WorkspaceAndProjectGenerator.class); @@ -325,6 +327,13 @@ public Path generateWorkspaceAndDependentProjects( schemeUngroupedTestTargets); } + if (projectGeneratorOptions.shouldGenerateTargetSchemes()) { + writeWorkspaceSchemesForTargets( + generatedProjectToPbxTargets, + targetToProjectPathMap + ); + } + writeWorkspaceSchemes( workspaceName, outputDirectory, @@ -457,14 +466,15 @@ private void generateProject( BuildTarget buildTarget = targetNode.getBuildTarget(); projectCellToBuildTargetsBuilder.put(rootCell.getCell(buildTarget.getCell()), buildTarget); Optional groupName = Optional.empty(); - if (targetNode.getDescription() instanceof AppleLibraryDescription) { - AppleLibraryDescriptionArg args = (AppleLibraryDescriptionArg)targetNode.getConstructorArg(); - groupName = args.getGroupName(); - } - if (targetNode.getDescription() instanceof AppleBundleDescription) { AppleBundleDescriptionArg args = (AppleBundleDescriptionArg)targetNode.getConstructorArg(); groupName = args.getGroupName(); + } else if (targetNode.getDescription() instanceof AppleLibraryDescription) { + AppleLibraryDescriptionArg args = (AppleLibraryDescriptionArg)targetNode.getConstructorArg(); + groupName = args.getGroupName(); + } else if (targetNode.getDescription() instanceof AppleTestDescription) { + AppleTestDescriptionArg args = (AppleTestDescriptionArg)targetNode.getConstructorArg(); + groupName = args.getGroupName(); } if (groupName.isPresent()) { @@ -1219,6 +1229,45 @@ private void writeWorkspaceSchemesForProjects( } } + private void writeWorkspaceSchemesForTargets( + ImmutableSetMultimap generatedProjectToPbxTargets, + ImmutableMap targetToProjectPathMap + ) throws IOException { + for (PBXProject project : generatedProjectToPbxTargets.keys()) { + ImmutableSet projectTargets = generatedProjectToPbxTargets.get(project); + ImmutableSetMultimap.Builder targetMapBuilder = ImmutableSetMultimap.builder(); + for (PBXTarget target : projectTargets) { + targetMapBuilder.put(target.getName(), target); + } + + ImmutableSetMultimap targetMap = targetMapBuilder.build(); + for (PBXTarget target : projectTargets) { + Path projectOutputDirectory = targetToProjectPathMap.get(target); + String schemeName = target.getName(); + String testName = schemeName + "Tests"; + ImmutableSet testTargets = targetMap.get(testName); + + ImmutableSet orderedBuildTargets = ImmutableSet.of(target); + SchemeGenerator schemeGenerator = + buildSchemeGenerator( + targetToProjectPathMap, + projectOutputDirectory, + schemeName, + Optional.empty(), + Optional.empty(), + orderedBuildTargets, + testTargets, + testTargets, + Optional.empty(), + Optional.empty(), + Optional.empty()); + + schemeGenerator.writeScheme(); + schemeGenerators.put(schemeName, schemeGenerator); + } + } + } + private void writeWorkspaceSchemes( String workspaceName, Path outputDirectory, diff --git a/src/com/facebook/buck/features/apple/project/XCodeProjectCommandHelper.java b/src/com/facebook/buck/features/apple/project/XCodeProjectCommandHelper.java index 0481e296de9..a737bddf767 100644 --- a/src/com/facebook/buck/features/apple/project/XCodeProjectCommandHelper.java +++ b/src/com/facebook/buck/features/apple/project/XCodeProjectCommandHelper.java @@ -143,6 +143,7 @@ public class XCodeProjectCommandHelper { private final String modulesToFocusOn; private final boolean combinedProject; private final boolean createProjectSchemes; + private final boolean createTargetSchemes; private final boolean dryRun; private final boolean readOnly; private final PathOutputPresenter outputPresenter; @@ -179,6 +180,7 @@ public XCodeProjectCommandHelper( String modulesToFocusOn, boolean combinedProject, boolean createProjectSchemes, + boolean createTargetSchemes, boolean dryRun, boolean readOnly, PathOutputPresenter outputPresenter, @@ -210,6 +212,7 @@ public XCodeProjectCommandHelper( this.modulesToFocusOn = modulesToFocusOn; this.combinedProject = combinedProject; this.createProjectSchemes = createProjectSchemes; + this.createTargetSchemes = createTargetSchemes; this.dryRun = dryRun; this.readOnly = readOnly; this.outputPresenter = outputPresenter; @@ -379,6 +382,7 @@ private ExitCode runXcodeProjectGenerator( .setShouldUseShortNamesForTargets(true) .setShouldCreateDirectoryStructure(combinedProject) .setShouldGenerateProjectSchemes(createProjectSchemes) + .setShouldGenerateTargetSchemes(createTargetSchemes) .build(); LOG.debug("Xcode project generation: Generates workspaces for targets"); diff --git a/src/com/facebook/buck/features/apple/project/XCodeProjectSubCommand.java b/src/com/facebook/buck/features/apple/project/XCodeProjectSubCommand.java index 2d851b99b69..fd5ab6badbb 100644 --- a/src/com/facebook/buck/features/apple/project/XCodeProjectSubCommand.java +++ b/src/com/facebook/buck/features/apple/project/XCodeProjectSubCommand.java @@ -39,6 +39,7 @@ public class XCodeProjectSubCommand extends ProjectSubCommand { private static final boolean DEFAULT_READ_ONLY_VALUE = false; private static final boolean DEFAULT_PROJECT_SCHEMES = false; + private static final boolean DEFAULT_TARGET_SCHEMES = false; private static final boolean DEFAULT_ABSOLUTE_HEADER_MAP_PATHS = false; private static final boolean DEFAULT_SHARED_LIBRARIES_AS_DYNAMIC_FRAMEWORKS = false; @@ -52,6 +53,11 @@ public class XCodeProjectSubCommand extends ProjectSubCommand { usage = "Generate an xcode scheme for each sub-project with its targets and tests.") private boolean projectSchemes = false; + @Option( + name = "--target-schemes", + usage = "Generate an xcode scheme for each sub-target with its targets and tests.") + private boolean targetSchemes = false; + @Option( name = "--focus", usage = @@ -139,6 +145,7 @@ public ExitCode run( modulesToFocusOn, combinedProject, getProjectSchemes(params.getBuckConfig()), + getTargetSchemes(params.getBuckConfig()), projectGeneratorParameters.isDryRun(), getReadOnly(params.getBuckConfig()), new PrintStreamPathOutputPresenter( @@ -183,6 +190,7 @@ public ExitCode run( projectGeneratorParameters.isWithoutDependenciesTests(), modulesToFocusOn, getProjectSchemes(params.getBuckConfig()), + getTargetSchemes(params.getBuckConfig()), projectGeneratorParameters.isDryRun(), getReadOnly(params.getBuckConfig()), new PrintStreamPathOutputPresenter( @@ -214,6 +222,12 @@ private boolean getProjectSchemes(BuckConfig buckConfig) { || buckConfig.getBooleanValue("project", "project_schemes", DEFAULT_PROJECT_SCHEMES); } + private boolean getTargetSchemes(BuckConfig buckConfig) { + // command line arg takes precedence over buck config + return targetSchemes + || buckConfig.getBooleanValue("project", "target_schemes", DEFAULT_TARGET_SCHEMES); + } + private boolean getReadOnly(BuckConfig buckConfig) { if (readOnly) { return readOnly; diff --git a/src/com/facebook/buck/features/apple/projectV2/ProjectGeneratorOptions.java b/src/com/facebook/buck/features/apple/projectV2/ProjectGeneratorOptions.java index 0b9555c9114..7fc4b02c672 100644 --- a/src/com/facebook/buck/features/apple/projectV2/ProjectGeneratorOptions.java +++ b/src/com/facebook/buck/features/apple/projectV2/ProjectGeneratorOptions.java @@ -34,6 +34,12 @@ default boolean shouldGenerateProjectSchemes() { return false; } + /** Create schemes for each target's contained build and test targets. */ + @Value.Default + default boolean shouldGenerateTargetSchemes() { + return false; + } + /** Generate read-only project files */ @Value.Default default boolean shouldGenerateReadOnlyFiles() { diff --git a/src/com/facebook/buck/features/apple/projectV2/XCodeProjectCommandHelper.java b/src/com/facebook/buck/features/apple/projectV2/XCodeProjectCommandHelper.java index b51055b3249..260ef651939 100644 --- a/src/com/facebook/buck/features/apple/projectV2/XCodeProjectCommandHelper.java +++ b/src/com/facebook/buck/features/apple/projectV2/XCodeProjectCommandHelper.java @@ -142,6 +142,7 @@ public class XCodeProjectCommandHelper { private final boolean withoutTests; private final boolean withoutDependenciesTests; private final boolean createProjectSchemes; + private final boolean createTargetSchemes; private final boolean dryRun; private final boolean readOnly; private final PathOutputPresenter outputPresenter; @@ -178,6 +179,7 @@ public XCodeProjectCommandHelper( boolean withoutDependenciesTests, String focus, boolean createProjectSchemes, + boolean createTargetSchemes, boolean dryRun, boolean readOnly, PathOutputPresenter outputPresenter, @@ -206,6 +208,7 @@ public XCodeProjectCommandHelper( this.withoutTests = withoutTests; this.withoutDependenciesTests = withoutDependenciesTests; this.createProjectSchemes = createProjectSchemes; + this.createTargetSchemes = createTargetSchemes; this.dryRun = dryRun; this.readOnly = readOnly; this.outputPresenter = outputPresenter; @@ -406,6 +409,7 @@ private ExitCode runXcodeProjectGenerator( appleConfig.shouldGenerateMissingUmbrellaHeaders()) .setShouldUseShortNamesForTargets(true) .setShouldGenerateProjectSchemes(createProjectSchemes) + .setShouldGenerateTargetSchemes(createTargetSchemes) .build(); LOG.debug("Xcode project generation: Generates workspaces for targets");