/* * Copyright 2013 the original author or authors. * * 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 org.gradle.nativeplatform.toolchain.internal.msvcpp; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import net.rubygrapefruit.platform.MissingRegistryEntryException; import net.rubygrapefruit.platform.SystemInfo; import net.rubygrapefruit.platform.WindowsRegistry; import org.gradle.api.Transformer; import org.gradle.internal.FileUtils; import org.gradle.internal.os.OperatingSystem; import org.gradle.nativeplatform.platform.Architecture; import org.gradle.nativeplatform.platform.internal.Architectures; import org.gradle.util.CollectionUtils; import org.gradle.util.TreeVisitor; import org.gradle.util.VersionNumber; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.util.*; public class DefaultVisualStudioLocator implements VisualStudioLocator { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultVisualStudioLocator.class); private static final String[] REGISTRY_BASEPATHS = { "SOFTWARE\\", "SOFTWARE\\Wow6432Node\\" }; private static final String REGISTRY_ROOTPATH_VC = "Microsoft\\VisualStudio\\SxS\\VC7"; private static final String PATH_COMMON = "Common7/"; private static final String PATH_COMMONTOOLS = PATH_COMMON + "Tools/"; private static final String PATH_COMMONIDE = PATH_COMMON + "IDE/"; private static final String PATH_BIN = "bin/"; private static final String PATH_INCLUDE = "include/"; private static final String COMPILER_FILENAME = "cl.exe"; private static final String DEFINE_ARMPARTITIONAVAILABLE = "_ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE"; private final Map<File, VisualStudioInstall> foundInstalls = new HashMap<File, VisualStudioInstall>(); private final OperatingSystem os; private final WindowsRegistry windowsRegistry; private final SystemInfo systemInfo; private VisualStudioInstall pathInstall; private boolean initialised; public DefaultVisualStudioLocator(OperatingSystem os, WindowsRegistry windowsRegistry, SystemInfo systemInfo) { this.os = os; this.windowsRegistry = windowsRegistry; this.systemInfo = systemInfo; } @Override public List<SearchResult> locateAllVisualStudioVersions() { initializeVisualStudioInstalls(); List<VisualStudioInstall> sortedInstalls = CollectionUtils.sort(foundInstalls.values(), new Comparator<VisualStudioInstall>() { @Override public int compare(VisualStudioInstall o1, VisualStudioInstall o2) { return o2.getVersion().compareTo(o1.getVersion()); } }); if (sortedInstalls.isEmpty()) { return Lists.newArrayList((SearchResult)new InstallNotFound("Could not locate a Visual Studio installation, using the Windows registry and system path.")); } else { return CollectionUtils.collect(sortedInstalls, new Transformer<SearchResult, VisualStudioInstall>() { @Override public SearchResult transform(VisualStudioInstall visualStudioInstall) { return new InstallFound(visualStudioInstall); } }); } } @Override public SearchResult locateDefaultVisualStudioInstall() { return locateDefaultVisualStudioInstall(null); } @Override public SearchResult locateDefaultVisualStudioInstall(File candidate) { initializeVisualStudioInstalls(); if (candidate != null) { return locateUserSpecifiedInstall(candidate); } return determineDefaultInstall(); } private void initializeVisualStudioInstalls() { if (!initialised) { locateInstallsInRegistry(); locateInstallInPath(); initialised = true; } } private void locateInstallsInRegistry() { for (String baseKey : REGISTRY_BASEPATHS) { locateInstallsInRegistry(baseKey); } } private void locateInstallsInRegistry(String baseKey) { List<String> visualCppVersions; try { visualCppVersions = windowsRegistry.getValueNames(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, baseKey + REGISTRY_ROOTPATH_VC); } catch (MissingRegistryEntryException e) { // No Visual Studio information available in the registry return; } for (String valueName : visualCppVersions) { if (!valueName.matches("\\d+\\.\\d+")) { // Ignore the other values continue; } File visualCppDir = new File(windowsRegistry.getStringValue(WindowsRegistry.Key.HKEY_LOCAL_MACHINE, baseKey + REGISTRY_ROOTPATH_VC, valueName)); visualCppDir = FileUtils.canonicalize(visualCppDir); File visualStudioDir = visualCppDir.getParentFile(); if (isVisualCpp(visualCppDir) && isVisualStudio(visualStudioDir)) { LOGGER.debug("Found Visual C++ {} at {}", valueName, visualCppDir); VersionNumber version = VersionNumber.parse(valueName); VisualCppInstall visualCpp = buildVisualCppInstall("Visual C++ " + valueName, visualStudioDir, visualCppDir, version); VisualStudioInstall visualStudio = new VisualStudioInstall(visualStudioDir, visualCpp); foundInstalls.put(visualStudioDir, visualStudio); } else { LOGGER.debug("Ignoring candidate Visual C++ directory {} as it does not look like a Visual C++ installation.", visualCppDir); } } } private void locateInstallInPath() { File compilerInPath = os.findInPath(COMPILER_FILENAME); if (compilerInPath == null) { LOGGER.debug("No visual c++ compiler found in system path."); return; } File visualCppDir = FileUtils.canonicalize(compilerInPath.getParentFile().getParentFile()); if (!isVisualCpp(visualCppDir)) { visualCppDir = visualCppDir.getParentFile(); if (!isVisualCpp(visualCppDir)) { LOGGER.debug("Ignoring candidate Visual C++ install for {} as it does not look like a Visual C++ installation.", compilerInPath); return; } } LOGGER.debug("Found Visual C++ install {} using system path", visualCppDir); File visualStudioDir = visualCppDir.getParentFile(); if (!foundInstalls.containsKey(visualStudioDir)) { VisualCppInstall visualCpp = buildVisualCppInstall("Visual C++ from system path", visualStudioDir, visualCppDir, VersionNumber.UNKNOWN); VisualStudioInstall visualStudio = new VisualStudioInstall(visualStudioDir, visualCpp); foundInstalls.put(visualStudioDir, visualStudio); } pathInstall = foundInstalls.get(visualStudioDir); } private SearchResult locateUserSpecifiedInstall(File candidate) { File visualStudioDir = FileUtils.canonicalize(candidate); File visualCppDir = new File(visualStudioDir, "VC"); if (!isVisualStudio(visualStudioDir) || !isVisualCpp(visualCppDir)) { LOGGER.debug("Ignoring candidate Visual C++ install for {} as it does not look like a Visual C++ installation.", candidate); return new InstallNotFound(String.format("The specified installation directory '%s' does not appear to contain a Visual Studio installation.", candidate)); } if (!foundInstalls.containsKey(visualStudioDir)) { VisualCppInstall visualCpp = buildVisualCppInstall("Visual C++ from user provided path", visualStudioDir, visualCppDir, VersionNumber.UNKNOWN); VisualStudioInstall visualStudio = new VisualStudioInstall(visualStudioDir, visualCpp); foundInstalls.put(visualStudioDir, visualStudio); } return new InstallFound(foundInstalls.get(visualStudioDir)); } private VisualCppInstall buildVisualCppInstall(String name, File vsPath, File basePath, VersionNumber version) { List<ArchitectureDescriptorBuilder> architectureDescriptorBuilders = Lists.newArrayList(); architectureDescriptorBuilders.add(ArchitectureDescriptorBuilder.X86_ON_X86); architectureDescriptorBuilders.add(ArchitectureDescriptorBuilder.AMD64_ON_X86); architectureDescriptorBuilders.add(ArchitectureDescriptorBuilder.IA64_ON_X86); architectureDescriptorBuilders.add(ArchitectureDescriptorBuilder.ARM_ON_X86); boolean isNativeAmd64 = systemInfo.getArchitecture() == SystemInfo.Architecture.amd64; if (isNativeAmd64) { // Prefer 64-bit tools when building on a 64-bit OS architectureDescriptorBuilders.add(ArchitectureDescriptorBuilder.AMD64_ON_AMD64); architectureDescriptorBuilders.add(ArchitectureDescriptorBuilder.X86_ON_AMD64); architectureDescriptorBuilders.add(ArchitectureDescriptorBuilder.ARM_ON_AMD64); } // populates descriptors, last descriptor in wins for a given architecture Map<Architecture, ArchitectureDescriptor> descriptors = Maps.newHashMap(); for (ArchitectureDescriptorBuilder architectureDescriptorBuilder : architectureDescriptorBuilders) { ArchitectureDescriptor descriptor = architectureDescriptorBuilder.buildDescriptor(basePath, vsPath); if (descriptor.isInstalled()) { descriptors.put(architectureDescriptorBuilder.architecture, descriptor); } } return new VisualCppInstall(name, version, descriptors); } private SearchResult determineDefaultInstall() { if (pathInstall != null) { return new InstallFound(pathInstall); } VisualStudioInstall candidate = null; for (VisualStudioInstall visualStudio : foundInstalls.values()) { if (candidate == null || visualStudio.getVersion().compareTo(candidate.getVersion()) > 0) { candidate = visualStudio; } } return candidate == null ? new InstallNotFound("Could not locate a Visual Studio installation, using the Windows registry and system path.") : new InstallFound(candidate); } private static boolean isVisualStudio(File candidate) { return new File(candidate, PATH_COMMON).isDirectory() && isVisualCpp(new File(candidate, "VC")); } private static boolean isVisualCpp(File candidate) { return new File(candidate, PATH_BIN + COMPILER_FILENAME).isFile(); } private static class InstallFound implements SearchResult { private final VisualStudioInstall install; public InstallFound(VisualStudioInstall install) { this.install = Preconditions.checkNotNull(install); } @Override public VisualStudioInstall getVisualStudio() { return install; } @Override public boolean isAvailable() { return true; } @Override public void explain(TreeVisitor<? super String> visitor) { } } private static class InstallNotFound implements SearchResult { private final String message; private InstallNotFound(String message) { this.message = message; } @Override public VisualStudioInstall getVisualStudio() { return null; } @Override public boolean isAvailable() { return false; } @Override public void explain(TreeVisitor<? super String> visitor) { visitor.node(message); } } static class DefaultArchitectureDescriptor implements ArchitectureDescriptor { private final List<File> paths; private final File binPath; private final File libPath; private final File includePath; private final String assemblerFilename; private final Map<String, String> definitions; private final File compilerPath; DefaultArchitectureDescriptor(List<File> paths, File binPath, File libPath, File compilerPath, File includePath, String assemblerFilename, Map<String, String> definitions) { this.paths = paths; this.binPath = binPath; this.libPath = libPath; this.includePath = includePath; this.assemblerFilename = assemblerFilename; this.definitions = definitions; this.compilerPath = compilerPath; } @Override public List<File> getPaths() { return paths; } @Override public File getBinaryPath() { return binPath; } @Override public File getLibraryPath() { return libPath; } @Override public File getIncludePath() { return includePath; } @Override public String getAssemblerFilename() { return assemblerFilename; } @Override public Map<String, String> getDefinitions() { return definitions; } @Override public boolean isInstalled() { return binPath.exists() && compilerPath.exists() && libPath.exists(); } } enum ArchitectureDescriptorBuilder { AMD64_ON_AMD64("amd64", "bin/amd64", "lib/amd64", "ml64.exe"), AMD64_ON_X86("amd64", "bin/x86_amd64", "lib/amd64", "ml64.exe") { @Override File getCrossCompilePath(File basePath) { return X86_ON_X86.getBinPath(basePath); } }, X86_ON_AMD64("x86", "bin/amd64_x86", "lib", "ml.exe") { @Override File getCrossCompilePath(File basePath) { return AMD64_ON_AMD64.getBinPath(basePath); } }, X86_ON_X86("x86", "bin", "lib", "ml.exe"), ARM_ON_AMD64("arm", "bin/amd64_arm", "lib/arm", "armasm.exe") { @Override File getCrossCompilePath(File basePath) { return AMD64_ON_AMD64.getBinPath(basePath); } @Override Map<String, String> getDefinitions() { Map<String, String> definitions = super.getDefinitions(); definitions.put(DEFINE_ARMPARTITIONAVAILABLE, "1"); return definitions; } }, ARM_ON_X86("arm", "bin/x86_arm", "lib/arm", "armasm.exe") { @Override File getCrossCompilePath(File basePath) { return X86_ON_X86.getBinPath(basePath); } @Override Map<String, String> getDefinitions() { Map<String, String> definitions = super.getDefinitions(); definitions.put(DEFINE_ARMPARTITIONAVAILABLE, "1"); return definitions; } }, IA64_ON_X86("ia64", "bin/x86_ia64", "lib/ia64", "ias.exe") { @Override File getCrossCompilePath(File basePath) { return X86_ON_X86.getBinPath(basePath); } }; final Architecture architecture; final String binPath; final String libPath; final String asmFilename; ArchitectureDescriptorBuilder(String architecture, String binPath, String libPath, String asmFilename) { this.binPath = binPath; this.libPath = libPath; this.asmFilename = asmFilename; this.architecture = Architectures.forInput(architecture); } File getBinPath(File basePath) { return new File(basePath, binPath); } File getLibPath(File basePath) { return new File(basePath, libPath); } File getCompilerPath(File basePath) { return new File(getBinPath(basePath), COMPILER_FILENAME); } File getCrossCompilePath(File basePath) { return null; } Map<String, String> getDefinitions() { return Maps.newHashMap(); } String getAsmFilename() { return asmFilename; } ArchitectureDescriptor buildDescriptor(File basePath, File vsPath) { File commonTools = new File(vsPath, PATH_COMMONTOOLS); File commonIde = new File(vsPath, PATH_COMMONIDE); List<File> paths = Lists.newArrayList(commonTools, commonIde); File crossCompilePath = getCrossCompilePath(basePath); if (crossCompilePath!=null) { paths.add(crossCompilePath); } File includePath = new File(basePath, PATH_INCLUDE); return new DefaultArchitectureDescriptor(paths, getBinPath(basePath), getLibPath(basePath), getCompilerPath(basePath), includePath, asmFilename, getDefinitions()); } } }