private void collectTransitiveClosure( PackageProviderForConfigurations packageProvider, Set<Label> reachableLabels, Label from) throws NoSuchThingException { if (!reachableLabels.add(from)) { return; } Target fromTarget = packageProvider.getTarget(from); if (fromTarget instanceof Rule) { Rule rule = (Rule) fromTarget; if (rule.getRuleClassObject().hasAttr("srcs", BuildType.LABEL_LIST)) { // TODO(bazel-team): refine this. This visits "srcs" reachable under *any* configuration, // not necessarily the configuration actually applied to the rule. We should correlate the // two. However, doing so requires faithfully reflecting the configuration transitions that // might happen as we traverse the dependency chain. // TODO(bazel-team): Why don't we use AbstractAttributeMapper#visitLabels() here? for (List<Label> labelsForConfiguration : AggregatingAttributeMapper.of(rule).visitAttribute("srcs", BuildType.LABEL_LIST)) { for (Label label : labelsForConfiguration) { collectTransitiveClosure( packageProvider, reachableLabels, from.resolveRepositoryRelative(label)); } } } if (rule.getRuleClass().equals("bind")) { Label actual = AggregatingAttributeMapper.of(rule).get("actual", BuildType.LABEL); if (actual != null) { collectTransitiveClosure(packageProvider, reachableLabels, actual); } } } }
@Override public List<Target> getLabelListAttr( QueryExpression caller, Target target, String attrName, String errorMsgPrefix) throws QueryException { Preconditions.checkArgument(target instanceof Rule); List<Target> result = new ArrayList<>(); Rule rule = (Rule) target; AggregatingAttributeMapper attrMap = AggregatingAttributeMapper.of(rule); Type<?> attrType = attrMap.getAttributeType(attrName); if (attrType == null) { // Return an empty list if the attribute isn't defined for this rule. return ImmutableList.of(); } for (Label label : attrMap.getReachableLabels(attrName, false)) { try { result.add(queryEnvironment.getTarget(label)); } catch (TargetNotFoundException e) { queryEnvironment.reportBuildFileError(caller, errorMsgPrefix + e.getMessage()); } } return result; }
@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); }
protected static PathFragment getTargetPath(Rule rule) throws RepositoryFunctionException { AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); String path = mapper.get("path", Type.STRING); PathFragment pathFragment = new PathFragment(path); if (!pathFragment.isAbsolute()) { throw new RepositoryFunctionException( new EvalException( rule.getLocation(), "In " + rule + " the 'path' attribute must specify an absolute path"), Transience.PERSISTENT); } return pathFragment; }
@Override public Iterable<String> getAttrAsString(Target target, String attrName) { Preconditions.checkArgument(target instanceof Rule); List<String> values = new ArrayList<>(); // May hold null values. Attribute attribute = ((Rule) target).getAttributeDefinition(attrName); if (attribute != null) { Type<?> attributeType = attribute.getType(); for (Object attrValue : AggregatingAttributeMapper.of((Rule) target) .visitAttribute(attribute.getName(), attributeType)) { // Ugly hack to maintain backward 'attr' query compatibility for BOOLEAN and TRISTATE // attributes. These are internally stored as actual Boolean or TriState objects but were // historically queried as integers. To maintain compatibility, we inspect their actual // value and return the integer equivalent represented as a String. This code is the // opposite of the code in BooleanType and TriStateType respectively. if (attributeType == BOOLEAN) { values.add(Type.BOOLEAN.cast(attrValue) ? "1" : "0"); } else if (attributeType == TRISTATE) { switch (BuildType.TRISTATE.cast(attrValue)) { case AUTO: values.add("-1"); break; case NO: values.add("0"); break; case YES: values.add("1"); break; default: throw new AssertionError("This can't happen!"); } } else { values.add(attrValue == null ? null : attrValue.toString()); } } } return values; }
protected SkyValue compute(Environment env, Rule rule) throws RepositoryFunctionException { // The output directory is always under .external-repository (to stay out of the way of // artifacts from this repository) and uses the rule's name to avoid conflicts with other // remote repository rules. For example, suppose you had the following WORKSPACE file: // // http_archive(name = "png", url = "http://example.com/downloads/png.tar.gz", sha256 = "...") // // This would download png.tar.gz to .external-repository/png/png.tar.gz. Path outputDirectory = getExternalRepositoryDirectory().getRelative(rule.getName()); FileValue directoryValue = createDirectory(outputDirectory, env); if (directoryValue == null) { return null; } AggregatingAttributeMapper mapper = AggregatingAttributeMapper.of(rule); URL url = null; try { url = new URL(mapper.get("url", Type.STRING)); } catch (MalformedURLException e) { throw new RepositoryFunctionException( new EvalException(rule.getLocation(), "Error parsing URL: " + e.getMessage()), Transience.PERSISTENT); } String sha256 = mapper.get("sha256", Type.STRING); HttpDownloader downloader = new HttpDownloader(url, sha256, outputDirectory); try { Path archiveFile = downloader.download(); outputDirectory = DecompressorFactory.create( rule.getTargetKind(), rule.getName(), archiveFile, outputDirectory) .decompress(); } catch (IOException e) { // Assumes all IO errors transient. throw new RepositoryFunctionException(e, Transience.TRANSIENT); } catch (DecompressorException e) { throw new RepositoryFunctionException(new IOException(e.getMessage()), Transience.TRANSIENT); } return new RepositoryValue(outputDirectory, directoryValue); }