/*
* 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.project_generator;
import com.facebook.buck.cxx.CxxPlatform;
import com.facebook.buck.log.Logger;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
/** Generates a set of Xcode build configurations from given cxxPlatform */
class CxxPlatformXcodeConfigGenerator {
public static final String SDKROOT = "SDKROOT";
public static final String CLANG_CXX_LANGUAGE_STANDARD = "CLANG_CXX_LANGUAGE_STANDARD";
public static final String CLANG_CXX_LIBRARY = "CLANG_CXX_LIBRARY";
public static final String OTHER_CPLUSPLUSFLAGS = "OTHER_CPLUSPLUSFLAGS";
public static final String ARCHS = "ARCHS";
public static final String DEBUG_BUILD_CONFIGURATION_NAME = "Debug";
public static final String PROFILE_BUILD_CONFIGURATION_NAME = "Profile";
public static final String RELEASE_BUILD_CONFIGURATION_NAME = "Release";
private static final Logger LOG = Logger.get(CxxPlatformXcodeConfigGenerator.class);
private CxxPlatformXcodeConfigGenerator() {}
public static ImmutableMap<String, ImmutableMap<String, String>>
getDefaultXcodeBuildConfigurationsFromCxxPlatform(
CxxPlatform cxxPlatform, Map<String, String> appendedConfig) {
ArrayList<String> notProcessedCxxFlags = new ArrayList<String>(cxxPlatform.getCxxflags());
LinkedHashMap<String, String> notProcessedAppendedConfig =
new LinkedHashMap<String, String>(appendedConfig);
ImmutableMap.Builder<String, String> configBuilder = ImmutableMap.builder();
setSdkRootAndDeploymentTargetValues(
configBuilder, cxxPlatform, notProcessedCxxFlags, notProcessedAppendedConfig);
removeArchsValue(notProcessedCxxFlags, notProcessedAppendedConfig);
setLanguageStandardValue(configBuilder, notProcessedCxxFlags, notProcessedAppendedConfig);
setCxxLibraryValue(notProcessedCxxFlags, notProcessedAppendedConfig, configBuilder);
setOtherCplusplusFlagsValue(configBuilder, notProcessedCxxFlags, notProcessedAppendedConfig);
setFlagsFromNotProcessedAppendedConfig(configBuilder, notProcessedAppendedConfig);
ImmutableMap<String, String> config = configBuilder.build();
return new ImmutableMap.Builder<String, ImmutableMap<String, String>>()
.put(DEBUG_BUILD_CONFIGURATION_NAME, config)
.put(PROFILE_BUILD_CONFIGURATION_NAME, config)
.put(RELEASE_BUILD_CONFIGURATION_NAME, config)
.build();
}
private static void setFlagsFromNotProcessedAppendedConfig(
ImmutableMap.Builder<String, String> configBuilder,
Map<String, String> notProcessedAppendedConfig) {
for (Map.Entry<String, String> entry :
ImmutableSet.copyOf(notProcessedAppendedConfig.entrySet())) {
if (entry.getValue().length() > 0) {
configBuilder.put(entry);
}
notProcessedAppendedConfig.remove(entry.getKey());
}
}
private static void setOtherCplusplusFlagsValue(
ImmutableMap.Builder<String, String> configBuilder,
List<String> notProcessedCxxFlags,
Map<String, String> notProcessedAppendedConfig) {
if (notProcessedCxxFlags.isEmpty()) {
return;
}
String otherCplusplusFlagsValue =
getOtherCplusplusFlags(notProcessedAppendedConfig, notProcessedCxxFlags);
configBuilder.put(OTHER_CPLUSPLUSFLAGS, otherCplusplusFlagsValue);
notProcessedAppendedConfig.remove(OTHER_CPLUSPLUSFLAGS);
}
private static void setCxxLibraryValue(
List<String> notProcessedCxxFlags,
Map<String, String> notProcessedAppendedConfig,
ImmutableMap.Builder<String, String> configBuilder) {
String clangCxxLibraryValue =
getConfigValueForKey(
CLANG_CXX_LIBRARY,
notProcessedCxxFlags,
"-stdlib=",
Optional.empty(),
notProcessedAppendedConfig);
if (clangCxxLibraryValue != null) {
configBuilder.put(CLANG_CXX_LIBRARY, clangCxxLibraryValue);
notProcessedAppendedConfig.remove(CLANG_CXX_LIBRARY);
}
}
private static void setSdkRootAndDeploymentTargetValues(
ImmutableMap.Builder<String, String> configBuilder,
CxxPlatform cxxPlatform,
List<String> notProcessedCxxFlags,
Map<String, String> notProcessedAppendedConfig) {
String sdkRootValue = getSdkRoot(cxxPlatform, notProcessedCxxFlags, notProcessedAppendedConfig);
if (sdkRootValue != null) {
configBuilder.put(SDKROOT, sdkRootValue);
notProcessedAppendedConfig.remove(SDKROOT);
setDeploymentTargetValue(
configBuilder, sdkRootValue, notProcessedCxxFlags, notProcessedAppendedConfig);
}
}
private static void removeArchsValue(
List<String> notProcessedCxxFlags, Map<String, String> notProcessedAppendedConfig) {
// we need to get rid of ARCH setting so Xcode would be able to let user switch between
// iOS simulator and iOS device without forcing the ARCH of resulting binary
getArchs(notProcessedAppendedConfig, notProcessedCxxFlags);
notProcessedAppendedConfig.remove(ARCHS);
}
private static void setLanguageStandardValue(
ImmutableMap.Builder<String, String> configBuilder,
List<String> notProcessedCxxFlags,
Map<String, String> notProcessedAppendedConfig) {
String clangCxxLanguageStandardValue =
getConfigValueForKey(
CLANG_CXX_LANGUAGE_STANDARD,
notProcessedCxxFlags,
"-std=",
Optional.empty(),
notProcessedAppendedConfig);
if (clangCxxLanguageStandardValue != null) {
configBuilder.put(CLANG_CXX_LANGUAGE_STANDARD, clangCxxLanguageStandardValue);
notProcessedAppendedConfig.remove(CLANG_CXX_LANGUAGE_STANDARD);
}
}
private static void setDeploymentTargetValue(
ImmutableMap.Builder<String, String> configBuilder,
String sdkRootValue,
List<String> notProcessedCxxFlags,
Map<String, String> notProcessedAppendedConfig) {
String deploymentTargetKey = sdkRootValue.toUpperCase() + "_DEPLOYMENT_TARGET"; // magic
String deploymentTargetValue =
getConfigValueForKey(
deploymentTargetKey,
notProcessedCxxFlags,
"-m", // format is like "-mmacosx-version-min=10.9"
Optional.of("-version-min="),
notProcessedAppendedConfig);
if (deploymentTargetValue != null) {
configBuilder.put(deploymentTargetKey, deploymentTargetValue);
notProcessedAppendedConfig.remove(deploymentTargetKey);
}
}
private static String getOtherCplusplusFlags(
Map<String, String> appendedConfig, List<String> notProcessesCxxFlags) {
String value = appendedConfig.get(OTHER_CPLUSPLUSFLAGS);
ArrayList<String> cPlusPlusFlags = new ArrayList<String>();
if (value != null) {
cPlusPlusFlags.add(value);
}
for (String item : notProcessesCxxFlags) {
cPlusPlusFlags.add(item);
}
value = Joiner.on(" ").join(cPlusPlusFlags);
notProcessesCxxFlags.removeAll(cPlusPlusFlags);
return value;
}
@Nullable
private static String getArchs(
Map<String, String> appendedConfig, List<String> notProcessesCxxFlags) {
String value = appendedConfig.get(ARCHS);
if (value == null) {
int indexOfArch = notProcessesCxxFlags.indexOf("-arch");
if (indexOfArch != -1) {
value = notProcessesCxxFlags.get(indexOfArch + 1);
notProcessesCxxFlags.remove(indexOfArch + 1);
notProcessesCxxFlags.remove(indexOfArch);
} else {
LOG.debug("Can't determine ARCH value");
}
}
return value;
}
@Nullable
private static String getSdkRoot(
CxxPlatform cxxPlatform,
List<String> notProcessesCxxFlags,
Map<String, String> appendedConfig) {
String value = appendedConfig.get(SDKROOT);
if (value == null) {
if (cxxPlatform.getFlavor().getName().startsWith("iphone")) {
value = "iphoneos";
} else if (cxxPlatform.getFlavor().getName().startsWith("macosx")) {
value = "macosx";
} else {
LOG.debug("Can't determine %s", SDKROOT);
}
int indexOfSysroot = notProcessesCxxFlags.indexOf("-isysroot");
if (indexOfSysroot != -1) {
notProcessesCxxFlags.remove(indexOfSysroot + 1);
notProcessesCxxFlags.remove(indexOfSysroot);
}
}
return value;
}
/**
* If appendedConfig has value for given key, it will be used. Otherwise, this method will attempt
* to extract value from cxxFlags.
*/
@Nullable
private static String getConfigValueForKey(
String key,
List<String> cxxFlags,
String prefix,
Optional<String> containmentString,
Map<String, String> appendedConfig) {
String value = appendedConfig.get(key);
if (value == null) {
int indexOfCxxLibrarySpec = -1;
for (String item : cxxFlags) {
if (containmentString.isPresent() && !item.contains(containmentString.get())) {
continue;
}
if (item.startsWith(prefix)) {
indexOfCxxLibrarySpec = cxxFlags.indexOf(item);
value = item.substring(item.indexOf('=') + 1);
break;
}
}
if (indexOfCxxLibrarySpec != -1) {
cxxFlags.remove(indexOfCxxLibrarySpec);
} else {
LOG.debug("Cannot determine value of %s", key);
}
}
return value;
}
}