Java tutorial
/* * Copyright 2016 The Bazel Authors. All rights reserved. * * 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.google.idea.blaze.cpp; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.idea.blaze.base.async.executor.BlazeExecutor; import com.google.idea.blaze.base.ideinfo.CToolchainIdeInfo; import com.google.idea.blaze.base.ideinfo.TargetIdeInfo; import com.google.idea.blaze.base.ideinfo.TargetKey; import com.google.idea.blaze.base.ideinfo.TargetMap; import com.google.idea.blaze.base.io.FileAttributeProvider; import com.google.idea.blaze.base.model.BlazeProjectData; import com.google.idea.blaze.base.model.primitives.ExecutionRootPath; import com.google.idea.blaze.base.model.primitives.LanguageClass; import com.google.idea.blaze.base.scope.BlazeContext; import com.google.idea.blaze.base.scope.Scope; import com.google.idea.blaze.base.scope.ScopedFunction; import com.google.idea.blaze.base.scope.output.IssueOutput; import com.google.idea.blaze.base.scope.scopes.TimingScope; import com.google.idea.blaze.base.sync.workspace.WorkspacePathResolver; import com.google.idea.blaze.base.targetmaps.SourceToTargetMap; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.jetbrains.cidr.lang.workspace.OCResolveConfiguration; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import javax.annotation.Nullable; final class BlazeConfigurationResolver { private static final class MapEntry { public final TargetKey targetKey; public final BlazeResolveConfiguration configuration; public MapEntry(TargetKey targetKey, BlazeResolveConfiguration configuration) { this.targetKey = targetKey; this.configuration = configuration; } } private static final Logger LOG = Logger.getInstance(BlazeConfigurationResolver.class); private final Project project; private ImmutableMap<TargetKey, BlazeResolveConfiguration> resolveConfigurations = ImmutableMap.of(); public BlazeConfigurationResolver(Project project) { this.project = project; } public void update(BlazeContext context, BlazeProjectData blazeProjectData) { ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap = BlazeResolveConfiguration .buildToolchainLookupMap(context, blazeProjectData.targetMap, blazeProjectData.reverseDependencies); ImmutableMap<File, VirtualFile> headerRoots = collectHeaderRoots(context, blazeProjectData, toolchainLookupMap); resolveConfigurations = buildBlazeConfigurationMap(context, blazeProjectData, toolchainLookupMap, headerRoots); } private static ImmutableMap<File, VirtualFile> collectHeaderRoots(BlazeContext parentContext, BlazeProjectData blazeProjectData, ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap) { // Type specification needed to avoid incorrect type inference during command line build. return Scope.push(parentContext, (ScopedFunction<ImmutableMap<File, VirtualFile>>) context -> { context.push(new TimingScope("Resolve header include roots")); Set<ExecutionRootPath> paths = collectExecutionRootPaths(blazeProjectData.targetMap, toolchainLookupMap); return doCollectHeaderRoots(context, blazeProjectData, paths); }); } private static ImmutableMap<File, VirtualFile> doCollectHeaderRoots(BlazeContext context, BlazeProjectData projectData, Set<ExecutionRootPath> rootPaths) { ConcurrentMap<File, VirtualFile> rootsMap = Maps.newConcurrentMap(); List<ListenableFuture<Void>> futures = Lists.newArrayListWithCapacity(rootPaths.size()); for (ExecutionRootPath path : rootPaths) { futures.add(submit(() -> { ImmutableList<File> possibleDirectories = projectData.workspacePathResolver .resolveToIncludeDirectories(path); for (File file : possibleDirectories) { VirtualFile vf = getVirtualFile(file); if (vf != null) { rootsMap.put(file, vf); } else if (!projectData.blazeRoots.isOutputArtifact(path) && FileAttributeProvider.getInstance().exists(file)) { // If it's not a blaze output file, we expect it to always resolve. LOG.info(String.format("Unresolved header root %s", file.getAbsolutePath())); } } return null; })); } try { Futures.allAsList(futures).get(); return ImmutableMap.copyOf(rootsMap); } catch (InterruptedException e) { Thread.currentThread().interrupt(); context.setCancelled(); } catch (ExecutionException e) { IssueOutput.error("Error resolving header include roots: " + e).submit(context); LOG.error("Error resolving header include roots", e); } return ImmutableMap.of(); } private static Set<ExecutionRootPath> collectExecutionRootPaths(TargetMap targetMap, ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap) { Set<ExecutionRootPath> paths = Sets.newHashSet(); for (TargetIdeInfo target : targetMap.targets()) { if (target.cIdeInfo != null) { paths.addAll(target.cIdeInfo.transitiveSystemIncludeDirectories); paths.addAll(target.cIdeInfo.transitiveIncludeDirectories); paths.addAll(target.cIdeInfo.transitiveQuoteIncludeDirectories); } } for (CToolchainIdeInfo toolchain : toolchainLookupMap.values()) { paths.addAll(toolchain.builtInIncludeDirectories); paths.addAll(toolchain.unfilteredToolchainSystemIncludes); } return paths; } @Nullable private static VirtualFile getVirtualFile(File file) { LocalFileSystem fileSystem = LocalFileSystem.getInstance(); VirtualFile vf = fileSystem.findFileByPathIfCached(file.getPath()); if (vf == null) { vf = fileSystem.findFileByIoFile(file); } return vf; } private ImmutableMap<TargetKey, BlazeResolveConfiguration> buildBlazeConfigurationMap( BlazeContext parentContext, BlazeProjectData blazeProjectData, ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap, ImmutableMap<File, VirtualFile> headerRoots) { // Type specification needed to avoid incorrect type inference during command line build. return Scope.push(parentContext, (ScopedFunction<ImmutableMap<TargetKey, BlazeResolveConfiguration>>) context -> { context.push(new TimingScope("Build C configuration map")); ConcurrentMap<CToolchainIdeInfo, File> compilerWrapperCache = Maps.newConcurrentMap(); List<ListenableFuture<MapEntry>> mapEntryFutures = Lists.newArrayList(); for (TargetIdeInfo target : blazeProjectData.targetMap.targets()) { if (target.kind.getLanguageClass() == LanguageClass.C) { ListenableFuture<MapEntry> future = submit(() -> createResolveConfiguration(target, toolchainLookupMap, headerRoots, compilerWrapperCache, blazeProjectData)); mapEntryFutures.add(future); } } ImmutableMap.Builder<TargetKey, BlazeResolveConfiguration> newResolveConfigurations = ImmutableMap .builder(); List<MapEntry> mapEntries; try { mapEntries = Futures.allAsList(mapEntryFutures).get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); context.setCancelled(); return ImmutableMap.of(); } catch (ExecutionException e) { IssueOutput.error("Could not build C resolve configurations: " + e).submit(context); LOG.error("Could not build C resolve configurations", e); return ImmutableMap.of(); } for (MapEntry mapEntry : mapEntries) { // Skip over labels that don't have C configuration data. if (mapEntry != null) { newResolveConfigurations.put(mapEntry.targetKey, mapEntry.configuration); } } return newResolveConfigurations.build(); }); } private static <T> ListenableFuture<T> submit(Callable<T> callable) { return BlazeExecutor.getInstance().submit(callable); } @Nullable private MapEntry createResolveConfiguration(TargetIdeInfo target, ImmutableMap<TargetKey, CToolchainIdeInfo> toolchainLookupMap, ImmutableMap<File, VirtualFile> headerRoots, ConcurrentMap<CToolchainIdeInfo, File> compilerWrapperCache, BlazeProjectData blazeProjectData) { TargetKey targetKey = target.key; CToolchainIdeInfo toolchainIdeInfo = toolchainLookupMap.get(targetKey); if (toolchainIdeInfo != null) { File compilerWrapper = findOrCreateCompilerWrapperScript(compilerWrapperCache, toolchainIdeInfo, blazeProjectData.workspacePathResolver, targetKey); if (compilerWrapper != null) { BlazeResolveConfiguration config = BlazeResolveConfiguration.createConfigurationForTarget(project, blazeProjectData.workspacePathResolver, headerRoots, blazeProjectData.targetMap.get(targetKey), toolchainIdeInfo, compilerWrapper); if (config != null) { return new MapEntry(targetKey, config); } } } return null; } @Nullable private static File findOrCreateCompilerWrapperScript(Map<CToolchainIdeInfo, File> compilerWrapperCache, CToolchainIdeInfo toolchainIdeInfo, WorkspacePathResolver workspacePathResolver, TargetKey targetKey) { File compilerWrapper = compilerWrapperCache.get(toolchainIdeInfo); if (compilerWrapper == null) { File cppExecutable = toolchainIdeInfo.cppExecutable.getAbsoluteOrRelativeFile(); if (cppExecutable != null && !cppExecutable.isAbsolute()) { cppExecutable = workspacePathResolver.resolveToFile(cppExecutable.getPath()); } if (cppExecutable == null) { String errorMessage = String.format("Unable to find compiler executable: %s for rule %s", toolchainIdeInfo.cppExecutable.toString(), targetKey); LOG.warn(errorMessage); compilerWrapper = null; } else { compilerWrapper = createCompilerExecutableWrapper(cppExecutable); if (compilerWrapper != null) { compilerWrapperCache.put(toolchainIdeInfo, compilerWrapper); } } } return compilerWrapper; } /** * Create a wrapper script that transforms the CLion compiler invocation into a safe invocation of * the compiler script that blaze uses. * * <p>CLion passes arguments to the compiler in an arguments file. The c toolchain compiler * wrapper script doesn't handle arguments files, so we need to move the compiler arguments from * the file to the command line. * * @param blazeCompilerExecutableFile blaze compiler wrapper * @return The wrapper script that CLion can call. */ @Nullable private static File createCompilerExecutableWrapper(File blazeCompilerExecutableFile) { try { File blazeCompilerWrapper = FileUtil.createTempFile("blaze_compiler", ".sh", true /* deleteOnExit */); if (!blazeCompilerWrapper.setExecutable(true)) { return null; } ImmutableList<String> compilerWrapperScriptLines = ImmutableList.of("#!/bin/bash", "", "# The c toolchain compiler wrapper script doesn't handle arguments files, so we", "# need to move the compiler arguments from the file to the command line.", "", "if [ $# -ne 2 ]; then", " echo \"Usage: $0 @arg-file compile-file\"", " exit 2;", "fi", "", "if [[ $1 != @* ]]; then", " echo \"Usage: $0 @arg-file compile-file\"", " exit 3;", "fi", "", " # Remove the @ before the arguments file path", "ARG_FILE=${1#@}", "# The actual compiler wrapper script we get from blaze", "EXE=" + blazeCompilerExecutableFile.getPath(), "# Read in the arguments file so we can pass the arguments on the command line.", "ARGS=`cat $ARG_FILE`", "$EXE $ARGS $2"); try (PrintWriter pw = new PrintWriter(blazeCompilerWrapper, UTF_8.name())) { compilerWrapperScriptLines.forEach(pw::println); } return blazeCompilerWrapper; } catch (IOException e) { return null; } } @Nullable public OCResolveConfiguration getConfigurationForFile(VirtualFile sourceFile) { SourceToTargetMap sourceToTargetMap = SourceToTargetMap.getInstance(project); List<TargetKey> targetsForSourceFile = Lists .newArrayList(sourceToTargetMap.getRulesForSourceFile(VfsUtilCore.virtualToIoFile(sourceFile))); if (targetsForSourceFile.isEmpty()) { return null; } // If a source file is in two different targets, // we can't possibly show how it will be interpreted in both contexts at the same time // in the IDE, so just pick the first target after we sort. targetsForSourceFile.sort((o1, o2) -> o1.toString().compareTo(o2.toString())); TargetKey targetKey = Iterables.getFirst(targetsForSourceFile, null); assert (targetKey != null); return resolveConfigurations.get(targetKey); } public List<? extends OCResolveConfiguration> getAllConfigurations() { return ImmutableList.copyOf(resolveConfigurations.values()); } }