/* * Copyright 2017-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.cli; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.log.Logger; import com.facebook.buck.rules.BuildInfoStore; import com.facebook.buck.rules.CachingBuildEngine; import com.facebook.buck.rules.CachingBuildEngineBuckConfig; import com.facebook.buck.rules.Cell; import java.io.IOException; import java.nio.channels.ClosedByInterruptException; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.Optional; public class MetadataChecker { private static final Logger LOG = Logger.get(MetadataChecker.class); // Utility class. Do not instantiate. private MetadataChecker() {} private static void writeMetadataStorage( ProjectFilesystem filesystem, Path metadataTypePath, CachingBuildEngine.MetadataStorage metadataStorage) throws IOException { filesystem.createParentDirs(metadataTypePath); // If metadata.type becomes corrupt (e.g., is created but not written), then buck-out is in an // unrecoverable state, since we don't know how the existing metadata is stored. Prevent this // from happening by writing to a temp file and then moving it into place. Path tempMetadataTypePath = filesystem.createTempFile("metadata", ".type"); filesystem.getPathForRelativePath(tempMetadataTypePath).toFile().setReadable(true, false); filesystem.writeContentsToPath(metadataStorage.toString(), tempMetadataTypePath); // Using {@link StandardCopyOption#ATOMIC_MOVE} would be ideal, but it's implementation-defined // whether overwrites can be done atomically. Since overwriting is what we need, let's hope // this is "atomic enough". filesystem.move(tempMetadataTypePath, metadataTypePath, StandardCopyOption.REPLACE_EXISTING); } // Special case deletion for scratch dir since build.log is open, which is a problem for Windows private static void deleteScratchDir(ProjectFilesystem filesystem) throws IOException { Path scratchDir = filesystem.getPathForRelativePath(filesystem.getBuckPaths().getScratchDir()); Files.walkFileTree( scratchDir, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (!file.getFileName().toString().equals("build.log")) { Files.delete(file); } return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { if (exc != null) { throw exc; } if (!dir.equals(scratchDir)) { Files.delete(dir); } return FileVisitResult.CONTINUE; } }); } private static void checkAndCleanIfNeededInner(Cell rootCell) throws IOException { for (Path cellPath : rootCell.getKnownRoots()) { Cell cell = rootCell.getCell(cellPath); LOG.debug("Checking metadata_storage for %s", cell.getFilesystem().getRootPath()); CachingBuildEngineBuckConfig config = cell.getBuckConfig().getView(CachingBuildEngineBuckConfig.class); CachingBuildEngine.MetadataStorage fromConfig = config.getBuildMetadataStorage(); ProjectFilesystem filesystem = cell.getFilesystem(); Path metadataPath = BuildInfoStore.getMetadataTypePath(filesystem); Optional<String> metadataString = filesystem.readFileIfItExists(metadataPath); if (metadataString.isPresent()) { // Only need consistency check if type file exists, otherwise it's a clean build. Optional<CachingBuildEngine.MetadataStorage> fromFile; try { fromFile = Optional.of(CachingBuildEngine.MetadataStorage.valueOf(metadataString.get())); } catch (IllegalArgumentException e) { fromFile = Optional.empty(); } if (fromFile.isPresent() && fromConfig == fromFile.get()) { // Our configured metadata storage backend is consistent with the last-used one. continue; } // An inconsistent metadata backend has been requested; we need to clean the repo. LOG.debug( "Changing metadata_storage from %s to %s, cleaning: %s", metadataString.get(), fromConfig, cell.getFilesystem().getRootPath()); deleteScratchDir(filesystem); filesystem.deleteRecursivelyIfExists(filesystem.getBuckPaths().getGenDir()); filesystem.deleteRecursivelyIfExists(filesystem.getBuckPaths().getTrashDir()); } writeMetadataStorage(filesystem, metadataPath, fromConfig); } } public static void checkAndCleanIfNeeded(Cell rootCell) throws IOException, InterruptedException { try { checkAndCleanIfNeededInner(rootCell); } catch (ClosedByInterruptException e) { Thread.currentThread().interrupt(); } } }