/* * Copyright 2016-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.cxx; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.rules.BuildRuleResolver; import com.facebook.buck.rules.ConstantToolProvider; import com.facebook.buck.rules.HashedFileTool; import com.facebook.buck.rules.Tool; import com.facebook.buck.rules.ToolProvider; import com.facebook.buck.util.Console; import com.facebook.buck.util.DefaultProcessExecutor; import com.facebook.buck.util.ProcessExecutor; import com.facebook.buck.util.ProcessExecutorParams; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.CharMatcher; import com.google.common.base.Splitter; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.io.IOException; import java.nio.file.Path; import java.util.EnumSet; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; /** A provide for the {@link Preprocessor} and {@link Compiler} C/C++ drivers. */ public abstract class CxxToolProvider<T> { private static final Logger LOG = Logger.get(CxxToolProvider.class); private static final Pattern CLANG_VERSION_PATTERN = Pattern.compile( "\\s*(" + // Ignore leading whitespace. "clang version [.0-9]*(\\s*\\(.*\\))?" + // Format used by opensource Clang. "|" + "Apple LLVM version [.0-9]*\\s*\\(clang-[.0-9]*\\)" + // Format used by Apple's clang. ")\\s*"); // Ignore trailing whitespace. private final ToolProvider toolProvider; private final Supplier<Type> type; private final LoadingCache<BuildRuleResolver, T> cache = CacheBuilder.newBuilder() .weakKeys() .build( new CacheLoader<BuildRuleResolver, T>() { @Override public T load(@Nonnull BuildRuleResolver resolver) throws Exception { return build(type.get(), toolProvider.resolve(resolver)); } }); private CxxToolProvider(ToolProvider toolProvider, Supplier<Type> type) { this.toolProvider = toolProvider; this.type = type; } /** Build using a {@link ToolProvider} and a required type. */ public CxxToolProvider(ToolProvider toolProvider, Type type) { this(toolProvider, Suppliers.ofInstance(type)); } /** * Build using a {@link Path} and an optional type. If the type is absent, the tool will be * executed to infer it. */ public CxxToolProvider(final Path path, Optional<Type> type) { this( new ConstantToolProvider(new HashedFileTool(path)), type.isPresent() ? Suppliers.ofInstance(type.get()) : Suppliers.memoize((Supplier<Type>) () -> getTypeFromPath(path))); } private static Type getTypeFromPath(Path path) { ProcessExecutorParams params = ProcessExecutorParams.builder().addCommand(path.toString()).addCommand("--version").build(); ProcessExecutor.Result result; try { ProcessExecutor processExecutor = new DefaultProcessExecutor(Console.createNullConsole()); result = processExecutor.launchAndExecute( params, EnumSet.of(ProcessExecutor.Option.EXPECTING_STD_OUT), Optional.empty(), Optional.empty(), Optional.empty()); } catch (InterruptedException | IOException e) { throw new RuntimeException(e); } if (result.getExitCode() != 0) { String commandString = params.getCommand().toString(); String message = result.getMessageForUnexpectedResult(commandString); LOG.error(message); throw new RuntimeException(message); } String stdout = result.getStdout().orElse(""); Iterable<String> lines = Splitter.on(CharMatcher.anyOf("\r\n")).split(stdout); LOG.debug("Output of %s: %s", params.getCommand(), lines); return getTypeFromVersionOutput(lines); } @VisibleForTesting protected static Type getTypeFromVersionOutput(Iterable<String> lines) { for (String line : lines) { Matcher matcher = CLANG_VERSION_PATTERN.matcher(line); if (matcher.matches()) { return Type.CLANG; } } return Type.GCC; } protected abstract T build(Type type, Tool tool); public T resolve(BuildRuleResolver resolver) { return cache.getUnchecked(resolver); } public Iterable<BuildTarget> getParseTimeDeps() { return toolProvider.getParseTimeDeps(); } public enum Type { CLANG, DEFAULT, GCC, WINDOWS } }