Consulta configurable (cquery)

Informar un problema Ver código fuente Nocturno · 8.4 · 8.3 · 8.2 · 8.1 · 8.0 · 7.6

cquery es una variante de query que controla correctamente los efectos de select() y las opciones de compilación en el gráfico de compilación.

Para ello, se ejecuta sobre los resultados de la fase de análisis de Bazel, que integra estos efectos. query, por el contrario, se ejecuta sobre los resultados de la fase de carga de Bazel, antes de que se evalúen las opciones.

Por ejemplo:

$ cat > tree/BUILD <<EOF
sh_library(
    name = "ash",
    deps = select({
        ":excelsior": [":manna-ash"],
        ":americana": [":white-ash"],
        "//conditions:default": [":common-ash"],
    }),
)
sh_library(name = "manna-ash")
sh_library(name = "white-ash")
sh_library(name = "common-ash")
config_setting(
    name = "excelsior",
    values = {"define": "species=excelsior"},
)
config_setting(
    name = "americana",
    values = {"define": "species=americana"},
)
EOF
# Traditional query: query doesn't know which select() branch you will choose,
# so it conservatively lists all of possible choices, including all used config_settings.
$ bazel query "deps(//tree:ash)" --noimplicit_deps
//tree:americana
//tree:ash
//tree:common-ash
//tree:excelsior
//tree:manna-ash
//tree:white-ash

# cquery: cquery lets you set build options at the command line and chooses
# the exact dependencies that implies (and also the config_setting targets).
$ bazel cquery "deps(//tree:ash)" --define species=excelsior --noimplicit_deps
//tree:ash (9f87702)
//tree:manna-ash (9f87702)
//tree:americana (9f87702)
//tree:excelsior (9f87702)

Cada resultado incluye un identificador único (9f87702) de la configuración con la que se compila el destino.

Dado que cquery se ejecuta en el gráfico de destino configurado, no tiene información sobre artefactos como las acciones de compilación ni acceso a las reglas de test_suite, ya que no son destinos configurados. Para obtener información sobre el primero, consulta aquery.

Sintaxis básica

Una llamada simple a cquery se ve de la siguiente manera:

bazel cquery "function(//target)"

La expresión de consulta "function(//target)" consta de lo siguiente:

  • function(...) es la función que se ejecutará en el destino. cquery admite la mayoría de las funciones de query, además de algunas nuevas.
  • //target es la expresión que se ingresa en la función. En este ejemplo, la expresión es un objetivo simple. Sin embargo, el lenguaje de consultas también permite anidar funciones. Consulta la Guía de consultas para ver ejemplos.

cquery requiere un destino para ejecutar las fases de carga y análisis. A menos que se especifique lo contrario, cquery analiza los destinos que se indican en la expresión de búsqueda. Consulta --universe_scope para consultar las dependencias de los destinos de compilación de nivel superior.

Configuraciones

La línea:

//tree:ash (9f87702)

significa que //tree:ash se compiló en una configuración con el ID 9f87702. Para la mayoría de los destinos, este es un hash opaco de los valores de las opciones de compilación que definen la configuración.

Para ver el contenido completo de la configuración, ejecuta el siguiente comando:

$ bazel config 9f87702

9f87702 es un prefijo del ID completo. Esto se debe a que los IDs completos son hashes SHA-256, que son largos y difíciles de seguir. cquery comprende cualquier prefijo válido de un ID completo, de manera similar a los hashes cortos de Git. Para ver los IDs completos, ejecuta $ bazel config.

Evaluación del patrón de segmentación

//foo tiene un significado diferente para cquery que para query. Esto se debe a que cquery evalúa los destinos configurados y el gráfico de compilación puede tener varias versiones configuradas de //foo.

En el caso de cquery, un patrón de destino en la expresión de consulta se evalúa para cada destino configurado con una etiqueta que coincide con ese patrón. El resultado es determinístico, pero cquery no garantiza el orden más allá del contrato de ordenamiento de la búsqueda principal.

Esto produce resultados más sutiles para las expresiones de búsqueda que con query. Por ejemplo, lo siguiente puede producir varios resultados:

# Analyzes //foo in the target configuration, but also analyzes
# //genrule_with_foo_as_tool which depends on an exec-configured
# //foo. So there are two configured target instances of //foo in
# the build graph.
$ bazel cquery //foo --universe_scope=//foo,//genrule_with_foo_as_tool
//foo (9f87702)
//foo (exec)

Si quieres declarar con precisión qué instancia consultar, usa la función config.

Consulta la documentación sobre los patrones de segmentación de query para obtener más información.

Funciones

De las funciones compatibles con query, cquery admite todas, excepto visible, siblings, buildfiles y tests.

cquery también introduce las siguientes funciones nuevas:

config

expr ::= config(expr, word)

El operador config intenta encontrar el destino configurado para la etiqueta que se indica en el primer argumento y la configuración que se especifica en el segundo argumento.

Los valores válidos para el segundo argumento son null o un hash de configuración personalizado. Los hashes se pueden recuperar de $ bazel config o del resultado de un cquery anterior.

Ejemplos:

$ bazel cquery "config(//bar, 3732cc8)" --universe_scope=//foo
$ bazel cquery "deps(//foo)"
//bar (exec)
//baz (exec)

$ bazel cquery "config(//baz, 3732cc8)"

Si no se encuentran todos los resultados del primer argumento en la configuración especificada, solo se devuelven los que se pueden encontrar. Si no se encuentran resultados en la configuración especificada, la consulta fallará.

Opciones

Opciones de compilación

cquery se ejecuta en una compilación normal de Bazel y, por lo tanto, hereda el conjunto de opciones disponibles durante una compilación.

Cómo usar las opciones de cquery

--universe_scope (lista separada por comas)

A menudo, las dependencias de los destinos configurados pasan por transiciones, lo que hace que su configuración difiera de la de su dependencia. Esta marca te permite consultar un destino como si se hubiera compilado como una dependencia o una dependencia transitiva de otro destino. Por ejemplo:

# x/BUILD
genrule(
     name = "my_gen",
     srcs = ["x.in"],
     outs = ["x.cc"],
     cmd = "$(locations :tool) $< >$@",
     tools = [":tool"],
)
cc_binary(
    name = "tool",
    srcs = ["tool.cpp"],
)

Las reglas de generación configuran sus herramientas en la configuración de ejecución, de modo que las siguientes búsquedas producirían los siguientes resultados:

Consulta Se compiló el destino Salida
bazel cquery "//x:tool" //x:tool //x:tool(targetconfig)
bazel cquery "//x:tool" --universe_scope="//x:my_gen" //x:my_gen //x:tool(execconfig)

Si se establece esta marca, se compila su contenido. Si no se configura, se compilan todos los destinos mencionados en la expresión de la consulta. El cierre transitivo de los destinos compilados se usa como el universo de la consulta. De cualquier manera, los destinos que se compilarán deben poder compilarse en el nivel superior (es decir, deben ser compatibles con las opciones de nivel superior). cquery devuelve resultados en el cierre transitivo de estos destinos de nivel superior.

Incluso si es posible compilar todos los destinos en una expresión de consulta en el nivel superior, puede ser beneficioso no hacerlo. Por ejemplo, establecer --universe_scope de forma explícita podría evitar la compilación de destinos varias veces en configuraciones que no te interesan. También podría ayudar a especificar qué versión de configuración de un destino buscas (ya que, actualmente, no es posible especificar esto de ninguna otra manera). Debes establecer esta marca si tu expresión de consulta es más compleja que deps(//foo).

--implicit_deps (booleano, valor predeterminado=True)

Si se establece esta marca como falsa, se filtran todos los resultados que no se establecen de forma explícita en el archivo BUILD y, en cambio, Bazel los establece en otro lugar. Esto incluye el filtrado de cadenas de herramientas resueltas.

--tool_deps (booleano, valor predeterminado=True)

Si se establece esta marca como falsa, se filtran todos los destinos configurados para los que la ruta desde el destino consultado hasta ellos cruza una transición entre la configuración del destino y las configuraciones que no son de destino. Si el destino consultado se encuentra en la configuración de destino, establecer --notool_deps solo devolverá los destinos que también se encuentren en la configuración de destino. Si el destino consultado se encuentra en una configuración no objetivo, establecer --notool_deps solo devolverá destinos que también se encuentren en configuraciones no objetivo. En general, este parámetro de configuración no afecta el filtrado de las cadenas de herramientas resueltas.

--include_aspects (booleano, valor predeterminado=True)

Incluye las dependencias agregadas por los aspectos.

Si esta marca está inhabilitada, cquery somepath(X, Y) y cquery deps(X) | grep 'Y' omiten Y si X solo depende de ella a través de un aspecto.

Formatos de salida

De forma predeterminada, cquery genera los resultados en una lista ordenada por dependencia de pares de etiquetas y configuración. También existen otras opciones para exponer los resultados.

Transiciones

--transitions=lite
--transitions=full

Las transiciones de configuración se usan para compilar destinos debajo de los destinos de nivel superior en configuraciones diferentes a las de los destinos de nivel superior.

Por ejemplo, un destino podría imponer una transición a la configuración de ejecución en todas las dependencias de su atributo tools. Estas se conocen como transiciones de atributos. Las reglas también pueden imponer transiciones en sus propias configuraciones, lo que se conoce como transiciones de clase de regla. Este formato de salida proporciona información sobre estas transiciones, como el tipo y el efecto que tienen en las opciones de compilación.

Este formato de salida se activa con la marca --transitions, que, de forma predeterminada, se establece en NONE. Se puede establecer en modo FULL o LITE. El modo FULL genera información sobre las transiciones de clases de reglas y de atributos, incluida una diferencia detallada de las opciones antes y después de la transición. El modo LITE genera la misma información sin la diferencia de opciones.

Salida de mensajes de protocolo

--output=proto

Esta opción hace que los destinos resultantes se impriman en formato de búfer de protocolo binario. La definición del búfer de protocolo se puede encontrar en src/main/protobuf/analysis_v2.proto.

CqueryResult es el mensaje de nivel superior que contiene los resultados de la cquery. Tiene una lista de mensajes de ConfiguredTarget y una lista de mensajes de Configuration. Cada ConfiguredTarget tiene un configuration_id cuyo valor es igual al del campo id del mensaje Configuration correspondiente.

--[no]proto:include_configurations

De forma predeterminada, los resultados de cquery devuelven información de configuración como parte de cada destino configurado. Si deseas omitir esta información y obtener un resultado de proto con el mismo formato que el resultado de proto de la consulta, establece esta marca en falso.

Consulta la documentación de la salida de proto de la consulta para obtener más opciones relacionadas con la salida de proto.

Resultado del gráfico

--output=graph

Esta opción genera resultados como un archivo .dot compatible con Graphviz. Consulta la documentación sobre el resultado del gráfico de query para obtener más información. cquery también admite --graph:node_limit y --graph:factored.

Archivos de salida

--output=files

Esta opción imprime una lista de los archivos de salida que produce cada destino que coincide con la consulta, similar a la lista que se imprime al final de una invocación de bazel build. El resultado solo contiene los archivos anunciados en los grupos de salida solicitados, según lo determina la marca --output_groups. Incluye archivos fuente.

Todas las rutas que emite este formato de salida son relativas a execroot, que se puede obtener a través de bazel info execution_root. Si existe el vínculo simbólico de conveniencia bazel-out, las rutas de acceso a los archivos del repositorio principal también se resuelven en relación con el directorio del espacio de trabajo.

Cómo definir el formato de salida con Starlark

--output=starlark

Este formato de salida llama a una función de Starlark para cada destino configurado en el resultado de la consulta y, luego, imprime el valor que devuelve la llamada. La marca --starlark:file especifica la ubicación de un archivo de Starlark que define una función llamada format con un solo parámetro, target. Se llama a esta función para cada Target en el resultado de la búsqueda. Como alternativa, para mayor comodidad, puedes especificar solo el cuerpo de una función declarada como def format(target): return expr con la marca --starlark:expr.

Dialecto de Starlark "cquery"

El entorno de Starlark de cquery difiere de un archivo BUILD o .bzl. Incluye todas las constantes y funciones integradas principales de Starlark, además de algunas específicas de cquery que se describen a continuación, pero no (por ejemplo) glob, native o rule, y no admite declaraciones de carga.

build_options(target)

build_options(target) devuelve un mapa cuyas claves son identificadores de opciones de compilación (consulta Configuraciones) y cuyos valores son sus valores de Starlark. En este mapa, se omiten las opciones de compilación cuyos valores no son valores legales de Starlark.

Si el destino es un archivo de entrada, build_options(target) devuelve None, ya que los destinos de archivos de entrada tienen una configuración nula.

providers(target)

providers(target) devuelve un mapa cuyas claves son los nombres de los proveedores (por ejemplo, "DefaultInfo") y cuyos valores son sus valores de Starlark. Los proveedores cuyos valores no son valores legales de Starlark se omiten de este mapa.

Ejemplos

Imprime una lista separada por espacios de los nombres base de todos los archivos que produce //foo:

  bazel cquery //foo --output=starlark \
    --starlark:expr="' '.join([f.basename for f in target.files.to_list()])"

Imprime una lista separada por espacios de las rutas de acceso de todos los archivos producidos por los destinos de rule en //bar y sus subpaquetes:

  bazel cquery 'kind(rule, //bar/...)' --output=starlark \
    --starlark:expr="' '.join([f.path for f in target.files.to_list()])"

Imprime una lista de las mnemónicas de todas las acciones registradas por //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="[a.mnemonic for a in target.actions]"

Imprime una lista de los resultados de la compilación registrados por un cc_library //baz.

  bazel cquery //baz --output=starlark \
    --starlark:expr="[f.path for f in target.output_groups.compilation_outputs.to_list()]"

Imprime el valor de la opción de línea de comandos --javacopt cuando se compila //foo.

  bazel cquery //foo --output=starlark \
    --starlark:expr="build_options(target)['//command_line_option:javacopt']"

Imprime la etiqueta de cada destino con exactamente un resultado. En este ejemplo, se usan funciones de Starlark definidas en un archivo.

  $ cat example.cquery

  def has_one_output(target):
    return len(target.files.to_list()) == 1

  def format(target):
    if has_one_output(target):
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

Imprime la etiqueta de cada destino que sea estrictamente de Python 3. En este ejemplo, se usan funciones de Starlark definidas en un archivo.

  $ cat example.cquery

  def format(target):
    p = providers(target)
    py_info = p.get("PyInfo")
    if py_info and py_info.has_py3_only_sources:
      return target.label
    else:
      return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

Extrae un valor de un objeto Provider definido por el usuario.

  $ cat some_package/my_rule.bzl

  MyRuleInfo = provider(fields={"color": "the name of a color"})

  def _my_rule_impl(ctx):
      ...
      return [MyRuleInfo(color="red")]

  my_rule = rule(
      implementation = _my_rule_impl,
      attrs = {...},
  )

  $ cat example.cquery

  def format(target):
    p = providers(target)
    my_rule_info = p.get("//some_package:my_rule.bzl%MyRuleInfo'")
    if my_rule_info:
      return my_rule_info.color
    return ""

  $ bazel cquery //baz --output=starlark --starlark:file=example.cquery

cquery vs. query

cquery y query se complementan y destacan en diferentes nichos. Ten en cuenta lo siguiente para decidir cuál es la opción adecuada para ti:

  • cquery sigue ramas select() específicas para modelar el gráfico exacto que compilas. query no sabe qué rama elige la compilación, por lo que realiza una sobreaproximación que incluye todas las ramas.
  • La precisión de cquery requiere compilar más del gráfico que query. Específicamente, cquery evalúa los destinos configurados, mientras que query solo evalúa los destinos. Esto lleva más tiempo y usa más memoria.
  • La interpretación de cquery del lenguaje de búsqueda introduce ambigüedad que query evita. Por ejemplo, si "//foo" existe en dos configuraciones, ¿cuál debería usar cquery "deps(//foo)"? La función config puede ayudarte con esto.
  • Como es una herramienta más nueva, cquery no admite ciertos casos de uso. Consulta Problemas conocidos para obtener más detalles.

Problemas conocidos

Todos los destinos que cquery "compila" deben tener la misma configuración.

Antes de evaluar las consultas, cquery activa una compilación hasta justo antes del punto en el que se ejecutarían las acciones de compilación. Los destinos que "compila" se seleccionan de forma predeterminada a partir de todas las etiquetas que aparecen en la expresión de búsqueda (esto se puede anular con --universe_scope). Estos deben tener la misma configuración.

Si bien, en general, comparten la configuración de "destino" de nivel superior, las reglas pueden cambiar su propia configuración con transiciones de borde entrantes. Aquí es donde cquery se queda corto.

Solución alternativa: Si es posible, establece --universe_scope en un alcance más estricto. Por ejemplo:

# This command attempts to build the transitive closures of both //foo and
# //bar. //bar uses an incoming edge transition to change its --cpu flag.
$ bazel cquery 'somepath(//foo, //bar)'
ERROR: Error doing post analysis query: Top-level targets //foo and //bar
have different configurations (top-level targets with different
configurations is not supported)

# This command only builds the transitive closure of //foo, under which
# //bar should exist in the correct configuration.
$ bazel cquery 'somepath(//foo, //bar)' --universe_scope=//foo

No se admite --output=xml.

Resultado no determinístico.

cquery no borra automáticamente el gráfico de compilación de los comandos anteriores y, por lo tanto, es propenso a recoger resultados de consultas anteriores. Por ejemplo, genquery ejerce una transición de exec en su atributo tools, es decir, configura sus herramientas en la configuración de exec.

A continuación, puedes ver los efectos persistentes de esa transición.

$ cat > foo/BUILD <<<EOF
genrule(
    name = "my_gen",
    srcs = ["x.in"],
    outs = ["x.cc"],
    cmd = "$(locations :tool) $< >$@",
    tools = [":tool"],
)
cc_library(
    name = "tool",
)
EOF

    $ bazel cquery "//foo:tool"
tool(target_config)

    $ bazel cquery "deps(//foo:my_gen)"
my_gen (target_config)
tool (exec_config)
...

    $ bazel cquery "//foo:tool"
tool(exec_config)

Solución alternativa: Cambia cualquier opción de inicio para forzar el nuevo análisis de los objetivos configurados. Por ejemplo, agrega --test_arg=&lt;whatever&gt; a tu comando de compilación.

Solución de problemas

Patrones de destino recursivos (/...)

Si encuentras lo siguiente:

$ bazel cquery --universe_scope=//foo:app "somepath(//foo:app, //foo/...)"
ERROR: Error doing post analysis query: Evaluation failed: Unable to load package '[foo]'
because package is not in scope. Check that all target patterns in query expression are within the
--universe_scope of this query.

Esto sugiere incorrectamente que el paquete //foo no está dentro del alcance, aunque --universe_scope=//foo:app lo incluye. Esto se debe a limitaciones de diseño en cquery. Como solución alternativa, incluye //foo/... de forma explícita en el alcance del universo:

$ bazel cquery --universe_scope=//foo:app,//foo/... "somepath(//foo:app, //foo/...)"

Si eso no funciona (por ejemplo, porque no se puede compilar algún destino en //foo/... con las marcas de compilación elegidas), desempaqueta manualmente el patrón en sus paquetes constituyentes con una consulta de procesamiento previo:

# Replace "//foo/..." with a subshell query call (not cquery!) outputting each package, piped into
# a sed call converting "<pkg>" to "//<pkg>:*", piped into a "+"-delimited line merge.
# Output looks like "//foo:*+//foo/bar:*+//foo/baz".
#
$  bazel cquery --universe_scope=//foo:app "somepath(//foo:app, $(bazel query //foo/...
--output=package | sed -e 's/^/\/\//' -e 's/$/:*/' | paste -sd "+" -))"