Skip to content

Commit

Permalink
Add repospec to the module
Browse files Browse the repository at this point in the history
- With this we can get the repospec directly from the module not the registry (no need for internet access)
- Since Repospec & Tag classes have an "attributes" field that can hold starlark values, an adapter was needed to serialize/deserialize starlark values

PiperOrigin-RevId: 523364539
Change-Id: Ifcdf0c6b4b6fbbcdae9c14a4b0cd7f53ae91c161
  • Loading branch information
SalmaSamy authored and copybara-github committed Apr 11, 2023
1 parent 036991e commit 23518b8
Show file tree
Hide file tree
Showing 34 changed files with 530 additions and 223 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
import com.google.devtools.build.lib.analysis.RuleDefinition;
import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
import com.google.devtools.build.lib.bazel.bzlmod.AttributeValues;
import com.google.devtools.build.lib.bazel.bzlmod.BazelDepGraphFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelLockFileFunction;
import com.google.devtools.build.lib.bazel.bzlmod.BazelModuleInspectorFunction;
Expand Down Expand Up @@ -233,7 +234,8 @@ public void workspaceInit(
public RepoSpec getRepoSpec(RepositoryName repoName) {
return RepoSpec.builder()
.setRuleClassName("local_config_platform")
.setAttributes(ImmutableMap.of("name", repoName.getName()))
.setAttributes(
AttributeValues.create(ImmutableMap.of("name", repoName.getName())))
.build();
}

Expand All @@ -251,11 +253,9 @@ public ResolutionReason getResolutionReason() {
SkyFunctions.MODULE_FILE,
new ModuleFileFunction(registryFactory, directories.getWorkspace(), builtinModules))
.addSkyFunction(
SkyFunctions.BAZEL_DEP_GRAPH,
new BazelDepGraphFunction(directories.getWorkspace(), registryFactory))
SkyFunctions.BAZEL_DEP_GRAPH, new BazelDepGraphFunction(directories.getWorkspace()))
.addSkyFunction(
SkyFunctions.BAZEL_LOCK_FILE,
new BazelLockFileFunction(directories.getWorkspace(), registryFactory))
SkyFunctions.BAZEL_LOCK_FILE, new BazelLockFileFunction(directories.getWorkspace()))
.addSkyFunction(SkyFunctions.BAZEL_MODULE_INSPECTION, new BazelModuleInspectorFunction())
.addSkyFunction(SkyFunctions.BAZEL_MODULE_RESOLUTION, new BazelModuleResolutionFunction())
.addSkyFunction(SkyFunctions.SINGLE_EXTENSION_EVAL, singleExtensionEvalFunction)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public RepoSpec build() {
return RepoSpec.builder()
.setBzlFile("@bazel_tools//tools/build_defs/repo:http.bzl")
.setRuleClassName("http_archive")
.setAttributes(attrBuilder.buildOrThrow())
.setAttributes(AttributeValues.create(attrBuilder.buildOrThrow()))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2023 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.devtools.build.lib.bazel.bzlmod;

import com.google.auto.value.AutoValue;
import com.google.common.collect.Maps;
import com.ryanharter.auto.value.gson.GenerateTypeAdapter;
import java.util.List;
import java.util.Map;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.Starlark;

/** Wraps a dictionary of attribute names and values. Always uses a dict to represent them */
@AutoValue
@GenerateTypeAdapter
public abstract class AttributeValues {

public static AttributeValues create(Dict<String, Object> attribs) {
return new AutoValue_AttributeValues(attribs);
}

public static AttributeValues create(Map<String, Object> attribs) {
return new AutoValue_AttributeValues(
Dict.immutableCopyOf(Maps.transformValues(attribs, AttributeValues::valueToStarlark)));
}

public abstract Dict<String, Object> attributes();

// TODO(salmasamy) this is a copy of Attribute::valueToStarlark, Maybe think of a better place?
private static Object valueToStarlark(Object x) {
// Is x a non-empty string_list_dict?
if (x instanceof Map) {
Map<?, ?> map = (Map<?, ?>) x;
if (!map.isEmpty() && map.values().iterator().next() instanceof List) {
Dict.Builder<Object, Object> dict = Dict.builder();
for (Map.Entry<?, ?> e : map.entrySet()) {
dict.put(e.getKey(), Starlark.fromJava(e.getValue(), null));
}
return dict.buildImmutable();
}
}
// For all other attribute values, shallow conversion is safe.
return Starlark.fromJava(x, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2023 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.devtools.build.lib.bazel.bzlmod;

import com.google.devtools.build.lib.cmdline.Label;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import net.starlark.java.eval.Dict;
import net.starlark.java.eval.EvalException;
import net.starlark.java.eval.Mutability;
import net.starlark.java.eval.Starlark;
import net.starlark.java.eval.StarlarkInt;
import net.starlark.java.eval.StarlarkList;

/** Helps serialize/deserialize {@link AttributeValues}, which contains Starlark values. */
public class AttributeValuesAdapter extends TypeAdapter<AttributeValues> {

@Override
public void write(JsonWriter out, AttributeValues attributeValues) throws IOException {
JsonObject jsonObject = new JsonObject();
for (Map.Entry<String, Object> entry : attributeValues.attributes().entrySet()) {
jsonObject.add(entry.getKey(), serializeObject(entry.getValue()));
}
out.jsonValue(jsonObject.toString());
}

@Override
public AttributeValues read(JsonReader in) throws IOException {
JsonObject jsonObject = JsonParser.parseReader(in).getAsJsonObject();
Dict.Builder<String, Object> dict = Dict.builder();
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
dict.put(entry.getKey(), deserializeObject(entry.getValue()));
}
return AttributeValues.create(dict.buildImmutable());
}

/**
* Starlark Object Types Bool Integer String Label List (Int, label, string) Dict (String,list) &
* (Label, String)
*/
private JsonElement serializeObject(Object obj) {
if (obj.equals(Starlark.NONE)) {
return JsonNull.INSTANCE;
} else if (obj instanceof Boolean) {
return new JsonPrimitive((Boolean) obj);
} else if (obj instanceof StarlarkInt) {
try {
return new JsonPrimitive(((StarlarkInt) obj).toInt("serialization into the lockfile"));
} catch (EvalException e) {
throw new IllegalArgumentException("Unable to parse StarlarkInt to Integer: " + e);
}
} else if (obj instanceof String || obj instanceof Label) {
return new JsonPrimitive(serializeObjToString(obj));
} else if (obj instanceof Dict) {
JsonObject jsonObject = new JsonObject();
for (Map.Entry<?, ?> entry : ((Dict<?, ?>) obj).entrySet()) {
jsonObject.add(serializeObjToString(entry.getKey()), serializeObject(entry.getValue()));
}
return jsonObject;
} else if (obj instanceof StarlarkList) {
JsonArray jsonArray = new JsonArray();
for (Object item : (StarlarkList<?>) obj) {
jsonArray.add(serializeObject(item));
}
return jsonArray;
} else {
throw new IllegalArgumentException("Unsupported type: " + obj.getClass());
}
}

private Object deserializeObject(JsonElement json) {
if (json == null || json.isJsonNull()) {
return Starlark.NONE;
} else if (json.isJsonPrimitive()) {
JsonPrimitive jsonPrimitive = json.getAsJsonPrimitive();
if (jsonPrimitive.isBoolean()) {
return jsonPrimitive.getAsBoolean();
} else if (jsonPrimitive.isNumber()) {
return StarlarkInt.of(jsonPrimitive.getAsInt());
} else if (jsonPrimitive.isString()) {
return deserializeStringToObject(jsonPrimitive.getAsString());
} else {
throw new IllegalArgumentException("Unsupported JSON primitive: " + jsonPrimitive);
}
} else if (json.isJsonObject()) {
JsonObject jsonObject = json.getAsJsonObject();
Dict.Builder<Object, Object> dict = Dict.builder();
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
dict.put(deserializeStringToObject(entry.getKey()), deserializeObject(entry.getValue()));
}
return dict.buildImmutable();
} else if (json.isJsonArray()) {
JsonArray jsonArray = json.getAsJsonArray();
List<Object> list = new ArrayList<>();
for (JsonElement item : jsonArray) {
list.add(deserializeObject(item));
}
return StarlarkList.copyOf(Mutability.IMMUTABLE, list);
} else {
throw new IllegalArgumentException("Unsupported JSON element: " + json);
}
}

/**
* Serializes an object (Label or String) to String A label is converted to a String as it is. A
* String is being modified with a delimiter to be easily differentiated from the label when
* deserializing.
*
* @param obj String or Label
* @return serialized object
*/
private String serializeObjToString(Object obj) {
if (obj instanceof Label) {
return ((Label) obj).getUnambiguousCanonicalForm();
}
return "--" + obj;
}

/**
* Deserializes a string to either a label or a String depending on the prefix. A string will have
* a delimiter at the start, else it is converted to a label.
*
* @param value String to be deserialized
* @return Object of type String of Label
*/
private Object deserializeStringToObject(String value) {
if (value.startsWith("--")) {
return value.substring(2);
}
return Label.parseCanonicalUnchecked(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ java_library(
name = "common",
srcs = [
"ArchiveRepoSpecBuilder.java",
"AttributeValues.java",
"ModuleKey.java",
"RepoSpec.java",
"Version.java",
Expand Down Expand Up @@ -51,6 +52,7 @@ java_library(
"TagClass.java",
],
deps = [
":common",
"//src/main/java/com/google/devtools/build/lib/cmdline",
"//src/main/java/com/google/devtools/build/lib/packages",
"//src/main/java/com/google/devtools/build/lib/starlarkbuildapi/repository",
Expand Down Expand Up @@ -129,6 +131,7 @@ java_library(
java_library(
name = "resolution_impl",
srcs = [
"AttributeValuesAdapter.java",
"BazelDepGraphFunction.java",
"BazelLockFileFunction.java",
"BazelModuleResolutionFunction.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,9 @@
public class BazelDepGraphFunction implements SkyFunction {

private final Path rootDirectory;
private final RegistryFactory registryFactory;

public BazelDepGraphFunction(Path rootDirectory, RegistryFactory registryFactory) {
public BazelDepGraphFunction(Path rootDirectory) {
this.rootDirectory = rootDirectory;
this.registryFactory = registryFactory;
}

@Override
Expand Down Expand Up @@ -105,7 +103,7 @@ public SkyValue compute(SkyKey skyKey, Environment env)
depGraph = selectionResult.getResolvedDepGraph();
if (starlarkSemantics.getBool(BuildLanguageOptions.ENABLE_LOCKFILE)) {
BazelLockFileFunction.updateLockedModule(
root.getModuleFileHash(), depGraph, rootDirectory, registryFactory, flags);
root.getModuleFileHash(), depGraph, rootDirectory, flags);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

package com.google.devtools.build.lib.bazel.bzlmod;

import static com.google.devtools.build.lib.bazel.bzlmod.GsonTypeAdapterUtil.LOCKFILE_GSON;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.common.collect.ImmutableList;
Expand All @@ -32,7 +33,6 @@
import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
import com.google.devtools.build.skyframe.SkyKey;
import com.google.devtools.build.skyframe.SkyValue;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import java.io.FileNotFoundException;
import java.io.IOException;
Expand All @@ -42,7 +42,6 @@
public class BazelLockFileFunction implements SkyFunction {

private final Path rootDirectory;
private final RegistryFactory registryFactory;

private static final BzlmodFlagsAndEnvVars EMPTY_FLAGS =
BzlmodFlagsAndEnvVars.create(
Expand All @@ -52,9 +51,8 @@ public class BazelLockFileFunction implements SkyFunction {
BazelLockFileValue.create(
BazelLockFileValue.LOCK_FILE_VERSION, "", EMPTY_FLAGS, ImmutableMap.of());

public BazelLockFileFunction(Path rootDirectory, RegistryFactory registryFactory) {
public BazelLockFileFunction(Path rootDirectory) {
this.rootDirectory = rootDirectory;
this.registryFactory = registryFactory;
}

@Override
Expand All @@ -70,10 +68,9 @@ public SkyValue compute(SkyKey skyKey, Environment env)
}

BazelLockFileValue bazelLockFileValue;
Gson gson = GsonTypeAdapterUtil.getLockfileGsonWithTypeAdapters(registryFactory);
try {
String json = FileSystemUtils.readContent(lockfilePath.asPath(), UTF_8);
bazelLockFileValue = gson.fromJson(json, BazelLockFileValue.class);
bazelLockFileValue = LOCKFILE_GSON.fromJson(json, BazelLockFileValue.class);
} catch (FileNotFoundException e) {
bazelLockFileValue = EMPTY_LOCKFILE;
} catch (IOException ex) {
Expand All @@ -92,7 +89,6 @@ public static void updateLockedModule(
String hashedModule,
ImmutableMap<ModuleKey, Module> resolvedDepGraph,
Path rootDirectory,
RegistryFactory registryFactory,
BzlmodFlagsAndEnvVars flags)
throws BazelModuleResolutionFunctionException {
RootedPath lockfilePath =
Expand All @@ -101,9 +97,8 @@ public static void updateLockedModule(
BazelLockFileValue value =
BazelLockFileValue.create(
BazelLockFileValue.LOCK_FILE_VERSION, hashedModule, flags, resolvedDepGraph);
Gson gson = GsonTypeAdapterUtil.getLockfileGsonWithTypeAdapters(registryFactory);
try {
FileSystemUtils.writeContent(lockfilePath.asPath(), UTF_8, gson.toJson(value));
FileSystemUtils.writeContent(lockfilePath.asPath(), UTF_8, LOCKFILE_GSON.toJson(value));
} catch (IOException e) {
throw new BazelModuleResolutionFunctionException(
ExternalDepsException.withCauseAndMessage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,5 @@ public static BazelLockFileValue create(

/** The post-selection dep graph retrieved from the lock file. */
public abstract ImmutableMap<ModuleKey, Module> getModuleDepGraph();

}
Loading

1 comment on commit 23518b8

@SalmaSamy
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bazel-io flag

Please sign in to comment.