@Override public SkyValue fetch(Rule rule, Path outputDirectory, Environment env) throws SkyFunctionException { AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); PathFragment pathFragment = new PathFragment(mapper.get("path", Type.STRING)); try { FileSystem fs = outputDirectory.getFileSystem(); if (fs.supportsSymbolicLinksNatively()) { outputDirectory.createSymbolicLink(pathFragment); } else { FileSystemUtils.createDirectoryAndParents(outputDirectory); FileSystemUtils.copyTreesBelow( fs.getPath(getTargetPath(rule, getWorkspace())), outputDirectory); } } catch (IOException e) { throw new RepositoryFunctionException( new IOException( "Could not create symlink to repository " + pathFragment + ": " + e.getMessage()), Transience.TRANSIENT); } FileValue repositoryValue = getRepositoryDirectory(outputDirectory, env); if (repositoryValue == null) { // TODO(bazel-team): If this returns null, we unnecessarily recreate the symlink above on the // second execution. return null; } if (!repositoryValue.isDirectory()) { throw new RepositoryFunctionException( new IOException(rule + " must specify an existing directory"), Transience.TRANSIENT); } return RepositoryValue.create(outputDirectory); }
/** * Symlinks a BUILD file from the local filesystem into the external repository's root. * * @param rule the rule that declares the build_file path. * @param workspaceDirectory the workspace root for the build. * @param directoryValue the FileValue corresponding to the external repository's root directory. * @param env the Skyframe environment. * @return the file value of the symlink created. * @throws * com.google.devtools.build.lib.bazel.repository.RepositoryFunction.RepositoryFunctionException * if the BUILD file specified does not exist or cannot be linked. */ protected RepositoryValue symlinkBuildFile( Rule rule, Path workspaceDirectory, FileValue directoryValue, Environment env) throws RepositoryFunctionException { AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); PathFragment buildFile = new PathFragment(mapper.get("build_file", Type.STRING)); Path buildFileTarget = workspaceDirectory.getRelative(buildFile); if (!buildFileTarget.exists()) { throw new RepositoryFunctionException( new EvalException( rule.getLocation(), String.format( "In %s the 'build_file' attribute does not specify an existing file " + "(%s does not exist)", rule, buildFileTarget)), Transience.PERSISTENT); } RootedPath rootedBuild; if (buildFile.isAbsolute()) { rootedBuild = RootedPath.toRootedPath( buildFileTarget.getParentDirectory(), new PathFragment(buildFileTarget.getBaseName())); } else { rootedBuild = RootedPath.toRootedPath(workspaceDirectory, buildFile); } SkyKey buildFileKey = FileValue.key(rootedBuild); FileValue buildFileValue; try { buildFileValue = (FileValue) env.getValueOrThrow( buildFileKey, IOException.class, FileSymlinkException.class, InconsistentFilesystemException.class); if (buildFileValue == null) { return null; } } catch (IOException | FileSymlinkException | InconsistentFilesystemException e) { throw new RepositoryFunctionException( new IOException("Cannot lookup " + buildFile + ": " + e.getMessage()), Transience.TRANSIENT); } Path buildFilePath = directoryValue.realRootedPath().asPath().getRelative("BUILD"); if (createSymbolicLink(buildFilePath, buildFileTarget, env) == null) { return null; } return RepositoryValue.createNew(directoryValue, buildFileValue); }
private static FileValue createSymbolicLink(Path from, Path to, Environment env) throws RepositoryFunctionException { try { if (!from.exists()) { from.createSymbolicLink(to); } } catch (IOException e) { throw new RepositoryFunctionException( new IOException( String.format( "Error creating symbolic link from %s to %s: %s", from, to, e.getMessage())), Transience.TRANSIENT); } SkyKey outputDirectoryKey = FileValue.key(RootedPath.toRootedPath(from, PathFragment.EMPTY_FRAGMENT)); try { return (FileValue) env.getValueOrThrow( outputDirectoryKey, IOException.class, FileSymlinkException.class, InconsistentFilesystemException.class); } catch (IOException | FileSymlinkException | InconsistentFilesystemException e) { throw new RepositoryFunctionException( new IOException(String.format("Could not access %s: %s", from, e.getMessage())), Transience.PERSISTENT); } }
protected RepositoryValue writeBuildFile(FileValue directoryValue, String contents) throws RepositoryFunctionException { Path buildFilePath = directoryValue.realRootedPath().asPath().getRelative("BUILD"); try { FileSystemUtils.writeContentAsLatin1(buildFilePath, contents); } catch (IOException e) { throw new RepositoryFunctionException(e, Transience.TRANSIENT); } return RepositoryValue.create(directoryValue); }
/** * Adds the repository's directory to the graph and, if it's a symlink, resolves it to an actual * directory. */ @Nullable public static FileValue getRepositoryDirectory(Path repositoryDirectory, Environment env) throws RepositoryFunctionException { SkyKey outputDirectoryKey = FileValue.key(RootedPath.toRootedPath(repositoryDirectory, PathFragment.EMPTY_FRAGMENT)); FileValue value; try { value = (FileValue) env.getValueOrThrow( outputDirectoryKey, IOException.class, FileSymlinkException.class, InconsistentFilesystemException.class); } catch (IOException | FileSymlinkException | InconsistentFilesystemException e) { throw new RepositoryFunctionException( new IOException("Could not access " + repositoryDirectory + ": " + e.getMessage()), Transience.PERSISTENT); } return value; }
/** * Get SkyKeys for the FileValues for the given {@code pathFragments}. To do this, we look for a * package lookup node for each path fragment, since package lookup nodes contain the "root" of a * package. The returned SkyKeys correspond to FileValues that may not exist in the graph. */ private Collection<SkyKey> getSkyKeysForFileFragments(Iterable<PathFragment> pathFragments) { Set<SkyKey> result = new HashSet<>(); Multimap<PathFragment, PathFragment> currentToOriginal = ArrayListMultimap.create(); for (PathFragment pathFragment : pathFragments) { currentToOriginal.put(pathFragment, pathFragment); } while (!currentToOriginal.isEmpty()) { Map<SkyKey, PathFragment> keys = new HashMap<>(); for (PathFragment pathFragment : currentToOriginal.keySet()) { keys.put( PackageLookupValue.key(PackageIdentifier.createInDefaultRepo(pathFragment)), pathFragment); } Map<SkyKey, SkyValue> lookupValues = graph.getSuccessfulValues(keys.keySet()); for (Map.Entry<SkyKey, SkyValue> entry : lookupValues.entrySet()) { PackageLookupValue packageLookupValue = (PackageLookupValue) entry.getValue(); if (packageLookupValue.packageExists()) { PathFragment dir = keys.get(entry.getKey()); Collection<PathFragment> originalFiles = currentToOriginal.get(dir); Preconditions.checkState(!originalFiles.isEmpty(), entry); for (PathFragment fileName : originalFiles) { result.add( FileValue.key(RootedPath.toRootedPath(packageLookupValue.getRoot(), fileName))); } currentToOriginal.removeAll(dir); } } Multimap<PathFragment, PathFragment> newCurrentToOriginal = ArrayListMultimap.create(); for (PathFragment pathFragment : currentToOriginal.keySet()) { PathFragment parent = pathFragment.getParentDirectory(); if (parent != null) { newCurrentToOriginal.putAll(parent, currentToOriginal.get(pathFragment)); } } currentToOriginal = newCurrentToOriginal; } return result; }