Просмотр исходного кода

Introduce version and build info stamping. (#4054)

This adds a defined Carbon version to the Bazel build and codebase that
can be used both to implement features like version checks and to report
a meaningful version on the command line. This replaces a hard-coded
string and a TODO in the driver.

As part of this, it adds support for defining the version in Bazel, and
special build flags for overriding relevant parts such as the
pre-release marker used. The exact structure and meaning of our version
string, including the pre-release parts, is implemented here in line
with the draft proposal:

https://docs.google.com/document/d/11S5VAPe5Pm_BZPlajWrqDDVr9qc7-7tS2VshqO0wWkk/edit?resourcekey=0-2YFC9Uvl4puuDnWlr2MmYw

This also introduces a workspace status command to the repository to
extract the git commit SHA and other information when building, and the
logic to stamp that into binaries as part of the version string when
useful. The technique used leverages weak symbols with whole archive
linking to allow a link-time override of unstamped data with stamped
data in the leaf executable. This makes building with `--stamp` a
reasonable default, especially for development builds. The CI system is
explicitly opted out of this as there it has no benefit.

Last but not least, all of these are wired into the install rules so
that we build installable packages with the version number in a
conventional place in the directory and filename.

---------

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Chandler Carruth 1 год назад
Родитель
Сommit
b51dc7f8e2

+ 16 - 0
.bazelrc

@@ -2,6 +2,22 @@
 # Exceptions. See /LICENSE for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+# Ensure all builds have Carbon's workspace status attached. We have carefully
+# factored the stamping done by this to avoid excessive build performance impact
+# and so enable stamping with it by default. CI and systems especially dependent
+# on caching should explicitly use `--nostamp`.
+build --workspace_status_command=./scripts/workspace_status.py
+build --stamp
+
+# Provide aliases for configuring the release and pre-release version being
+# built. For documentation of these flags, see //bazel/version/BUILD.
+build --flag_alias=release=//bazel/version:release
+build --flag_alias=pre_release=//bazel/version:pre_release
+build --flag_alias=rc_number=//bazel/version:rc_number
+build --flag_alias=beta_number=//bazel/version:beta_number
+build --flag_alias=alpha_number=//bazel/version:alpha_number
+build --flag_alias=nightly_date=//bazel/version:nightly_date
+
 # Support running clang-tidy with:
 #   bazel build --config=clang-tidy -k //...
 # See: https://github.com/erenon/bazel_clang_tidy

+ 3 - 0
.github/workflows/tests.yaml

@@ -294,6 +294,9 @@ jobs:
           # https://discord.com/channels/655572317891461132/707150492370862090/1151605725576056934
           build --jobs=32
 
+          # Avoid any cache impact from build stamping in CI.
+          build --nostamp
+
           # General build options.
           build --verbose_failures
           test --test_output=errors

+ 97 - 0
bazel/version/BUILD

@@ -0,0 +1,97 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "int_flag", "string_flag")
+
+package(default_visibility = ["//toolchain/install:__pkg__"])
+
+exports_files(["gen_tmpl.py"])
+
+# Several flags are provided for customizing the exact version used for the
+# build of Carbon. Each of these is documented here, but rather than using the
+# label-based names in Bazel invocations (`bazel build --//bazel/version:flag`)
+# we suggest using the flag aliases provided in the project's `.bazelrc` and we
+# document the flags using those aliases. The aliases match the local flag names
+# here.
+#
+# For more details on the versioning scheme used by Carbon, see:
+# - https://docs.google.com/document/d/11S5VAPe5Pm_BZPlajWrqDDVr9qc7-7tS2VshqO0wWkk/edit?resourcekey=0-2YFC9Uvl4puuDnWlr2MmYw
+#   TODO: Replace with path to the markdown once this lands.
+#
+# First, we provide a flag to enable a release version: `--release`. It is
+# disabled by default, and if enabled it must be the only version flag used.
+bool_flag(
+    name = "release",
+    build_setting_default = False,
+)
+
+# A `--pre_release=KIND` flag where `KIND` must be one of:
+# - `rc` -- a release candidate version.
+#    Example: `--pre_release=rc --rc_number=2`
+# - `beta` -- a beta version.
+#    Example: `--pre_release=beta --beta_number=2`
+# - `alpha` -- an alpha version.
+#    Example: `--pre_release=alpha --alpha_number=2`
+# - `nightly -- a nightly version.
+#    Example: `--pre_release=nightly --nightly_date=2024.06.17`
+# - `dev` -- the default, a development build.
+#    Example: `--pre_release=dev`
+#
+# This flag cannot be used along with `--release`, and for all but the `dev`
+# kind must be combined with one of the below flags to specify further details
+# of the version.
+string_flag(
+    name = "pre_release",
+    build_setting_default = "dev",
+    values = [
+        "rc",
+        "beta",
+        "alpha",
+        "nightly",
+        "dev",
+    ],
+)
+
+# `--rc_number=N` sets the release candidate number to `N`. Requires `--pre_release=rc`.
+int_flag(
+    name = "rc_number",
+    build_setting_default = -1,
+)
+
+# When `--pre_release=beta` is used, `--beta_number=N` set's the beta
+# pre-release number to `N`.
+#
+# An error if used without `--pre_release=beta`.
+int_flag(
+    name = "beta_number",
+    build_setting_default = -1,
+)
+
+# When `--pre_release=alpha` is used, `--alpha_number=N` set's the alpha
+# pre-release number to `N`.
+#
+# An error if used without `--pre_release=alpha`.
+int_flag(
+    name = "alpha_number",
+    build_setting_default = -1,
+)
+
+# When `--pre_release=nightly` is used, `--nightly_date=YYYY.MM.DD` set's the
+# nightly build date. The value for this flag must be a string with the exact
+# format of `YYYY.MM.DD`.
+#
+# An error if used without `--pre_release=nightly`.
+string_flag(
+    name = "nightly_date",
+    build_setting_default = "",
+)
+
+# A config setting to observe the value of the `--stamp` command line flag
+# within starlark with a macro and `select`. This is a workaround suggested for
+# a Bazel issue: https://github.com/bazelbuild/bazel/issues/11164
+config_setting(
+    name = "internal_stamp_flag_detect",
+    values = {"stamp": "1"},
+    visibility = ["//visibility:public"],
+)

+ 69 - 0
bazel/version/compute_version.bzl

@@ -0,0 +1,69 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Compute the version string."""
+
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
+load("//:version_base.bzl", "version_base")
+
+def _validate_nightly_date(date):
+    date_components = date.split(".", 2)
+    if len(date_components) != 3:
+        fail("Must provide a nightly date in 'YYYY.MM.DD' format, found '{}'.".format(date))
+    year = date_components[0]
+    if len(year) != 4 or not year.isdigit():
+        fail("The nightly date year was not a sequence of four digits.")
+    month = date_components[1]
+    if len(month) != 2 or not month.isdigit():
+        fail("The nightly date month was not a sequence of two digits.")
+    day = date_components[2]
+    if len(day) != 2 or not day.isdigit():
+        fail("The nightly date day was not a sequence of two digits.")
+
+def compute_version(ctx):
+    """Compute the version string.
+
+    Args:
+        ctx: The context for a rule computing the version.
+
+    Returns:
+        The version string.
+    """
+    version = version_base
+
+    # See if we need to append a pre-release suffix to the version.
+    #
+    # TODO: We should more fully check for erroneous combinations of flags here
+    # to help ensure users don't get surprising results.
+    if not ctx.attr._release_flag[BuildSettingInfo].value:
+        pre_release = ctx.attr._pre_release_flag[BuildSettingInfo].value
+        pre_release_numbers = {
+            "alpha": ctx.attr._alpha_number_flag[BuildSettingInfo].value,
+            "beta": ctx.attr._beta_number_flag[BuildSettingInfo].value,
+            "rc": ctx.attr._rc_number_flag[BuildSettingInfo].value,
+        }
+        if pre_release in pre_release_numbers:
+            number = pre_release_numbers[pre_release]
+            if number < 0:
+                fail("Must provide a non-negative {} number when building that pre-release.".format(pre_release))
+            version += "-{0}.{1}".format(pre_release, number)
+        elif pre_release == "nightly":
+            date = ctx.attr._nightly_date_flag[BuildSettingInfo].value
+            _validate_nightly_date(date)
+            version += "-0.nightly.{}".format(date)
+        elif pre_release == "dev":
+            version += "-0.dev"
+        else:
+            fail("Invalid pre-release flag: " + pre_release)
+
+    return version
+
+VERSION_ATTRS = {
+    "_alpha_number_flag": attr.label(default = ":alpha_number"),
+    "_beta_number_flag": attr.label(default = ":beta_number"),
+    "_nightly_date_flag": attr.label(default = ":nightly_date"),
+    "_pre_release_flag": attr.label(default = ":pre_release"),
+    "_rc_number_flag": attr.label(default = ":rc_number"),
+    "_release_flag": attr.label(default = ":release"),
+}

+ 112 - 0
bazel/version/gen_tmpl.py

@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+
+"""Generate a file from a template, substituting the provided key/value pairs.
+
+The file format should match Python's `string.Template` substitution rules:
+- `$$` for a literal `$`
+- `$identifier` for some key `identifier` to be substituted
+- `${identifier}` when adjacent text would be interpreted as part of the
+  identifier.
+
+The keys must be strings that are valid identifiers: `[_A-Za-z][_A-Za-z0-9]*`
+
+The values may not contain newlines or any vertical whitespace.
+
+The initial key/value pairs are read from the command line using repeated
+`--substitute=KEY=DEFAULT-VALUE` flags.
+
+Updated values for those keys will be read from any files provided to the
+`--status-file` flag. This flag can be given multiple times and the values will
+be read and updated from the files in order, meaning the last file's value will
+win. New keys are never read from these files. The file format parsed is Bazel's
+[status file format](https://bazel.build/docs/user-manual#workspace-status):
+each line is a single entry starting with a key using only characters `[_A-Z]`,
+one space character, and the rest of the line is the value. To assist with using
+Bazel status files, if the key parsed from the file begins with `STABLE_`, that
+prefix is removed. Any keys which are present in the substitutions provided on
+the command line will have their value updated with the string read from the
+file.
+"""
+
+__copyright__ = """
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""
+
+import argparse
+import sys
+from pathlib import Path
+from string import Template
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(__doc__)
+    parser.add_argument(
+        "--template",
+        metavar="FILE",
+        type=Path,
+        required=True,
+        help="The template source file to use.",
+    )
+    parser.add_argument(
+        "--output",
+        metavar="FILE",
+        type=Path,
+        required=True,
+        help="The output source file to produce.",
+    )
+    parser.add_argument(
+        "--substitution",
+        metavar="KEY=DEFAULT-VALUE",
+        action="append",
+        help="A substitution that should be supported and its default value.",
+    )
+    parser.add_argument(
+        "--status-file",
+        metavar="FILE",
+        type=Path,
+        action="append",
+        default=[],
+        help="A file of key/value updates in Bazel's status file format.",
+    )
+    parser.add_argument("-v", "--verbose", action="store_true")
+    args = parser.parse_args()
+
+    # Collect the supported substitutions from the command line.
+    substitutions = {}
+    for substitution_arg in args.substitution:
+        key, value = substitution_arg.split("=", 1)
+        substitutions.update({key: value})
+
+    # Read either of the two status files provided to build up substitutions,
+    # with the stable file last so its values override any duplicates.
+    for status_file in args.status_file:
+        if args.verbose:
+            print(f"Reading status file: {status_file}", file=sys.stderr)
+        for line in status_file.open():
+            # Remove line endings.
+            line = line.rstrip("\r\n")
+            # Exactly matches our pattern
+            (key, value) = line.split(" ", 1)
+            key = key.removeprefix("STABLE_")
+            if key in substitutions:
+                if args.verbose:
+                    print(f"Parsed: '{key}': '{value}'", file=sys.stderr)
+                substitutions.update({key: value})
+
+    if args.verbose:
+        print(f"Reading template file: {args.template}", file=sys.stderr)
+    with open(args.template) as template_file:
+        template = template_file.read()
+
+    result = Template(template).substitute(substitutions)
+
+    if args.verbose:
+        print(f"Writing output file: {args.output}", file=sys.stderr)
+    with open(args.output, mode="w") as output_file:
+        output_file.write(result)
+
+
+if __name__ == "__main__":
+    main()

+ 144 - 0
bazel/version/rules.bzl

@@ -0,0 +1,144 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Rule to expand Bazel templates with version and build information.
+
+This rule takes a source code template and turns that into a specific source
+code output, substituting version information and build information from Bazel's
+`stable-status.txt` and `volatile-status.txt` produced by the
+`workpsace_status_command` during the build. When stamping is disabled, the
+build information is replaced with constant values to provide better caching.
+
+The template files should use Python's "template strings" syntax[1]. These rules
+provide a fixed set of keys whose values will be substituted, and those keys
+will always be substituted with something. They will have the value in the
+stable status file if present, otherwise the value in the volatile status file
+if present, otherwise the value "unknown". When reading keys from the status
+files, a prefix of `STABLE_` will be removed from the key if present.
+
+[1]: https://docs.python.org/3/library/string.html#template-strings
+
+The substituted keys, and any guidance on values:
+- `VERSION` (the version string for Carbon)
+- `BUILD_EMBED_LABEL` (value of --embed_label)
+- `BUILD_HOST` (the name of the host machine running the build)
+- `BUILD_USER` (the name of the user running the build)
+- `GIT_COMMIT_SHA` (output of `git parse-rev --short HEAD` or `unknown`)
+- `GIT_DIRTY_SUFFIX` (`.dirty` if dirty client state or `` if unknown)
+- `BUILD_TIMESTAMP` (the time of the build in seconds since the Unix Epoch)
+- `ATTRIBUTE` (an optional attribute to apply to definitions, defaults to empty)
+"""
+
+load(":compute_version.bzl", "compute_version")
+
+_STAMP_DOC = """
+Follows behavior of the common 'stamp' attributes on rules. Set to 1 or 0 to
+force stamping with actual build info on or off respectively, and to -1 to
+follow the value of the command line flag `--stamp`.
+"""
+
+def _is_exec_config(ctx):
+    """Detect if this is the exec configuration, previously known as "host".
+
+    Sadly, there is not yet a supported way to detect this so replicate the
+    hacks others currently use. Bazel issue:
+    https://github.com/bazelbuild/bazel/issues/14444
+    """
+    return "-exec" in ctx.bin_dir.path or "/host/" in ctx.bin_dir.path
+
+def _expand_version_build_info_impl(ctx):
+    """Generates a file from a template, substituting version and build info."""
+    inputs = [ctx.file.template]
+
+    # The substitutions provided and their default values.
+    substitutions = {
+        "BUILD_EMBED_LABEL": "unknown",
+        "BUILD_HOST": "unknown",
+        "BUILD_TIMESTAMP": "unknown",
+        "BUILD_USER": "unknown",
+        "GIT_COMMIT_SHA": "unknown",
+        "GIT_DIRTY_SUFFIX": "",
+        "MAKE_WEAK": "0",
+        "VERSION": compute_version(ctx),
+    }
+    substitutions.update(ctx.attr.substitutions)
+
+    arguments = [
+        "--template=" + ctx.file.template.path,
+        "--output=" + ctx.outputs.out.path,
+    ] + [
+        "--substitution=" + key + "=" + value
+        for key, value in substitutions.items()
+    ]
+
+    # We only want to allow stamping outside of the exec configuration.
+    if not _is_exec_config(ctx):
+        # Look at the attribute.
+        stamp = ctx.attr.stamp
+
+        # If requested, use the command line flag to select.
+        if stamp == -1:
+            # Set the default from `--stamp` / `--nostamp` command line flag,
+            # which we detect through a macro and `config_setting`, and pipe
+            # through an attribute.
+            stamp = 1 if ctx.attr.internal_stamp_flag_detect else 0
+
+        # Add the status files if stamping.
+        if stamp == 1:
+            inputs += [
+                ctx.info_file,
+                ctx.version_file,
+            ]
+            arguments += [
+                "--status-file=" + ctx.info_file.path,
+                "--status-file=" + ctx.version_file.path,
+            ]
+
+    ctx.actions.run(
+        inputs = inputs,
+        outputs = [ctx.outputs.out],
+        executable = ctx.executable._gen_tmpl_tool,
+        arguments = arguments,
+        progress_message = "Generating templated source file: " +
+                           ctx.outputs.out.short_path,
+    )
+
+expand_version_build_info_internal = rule(
+    implementation = _expand_version_build_info_impl,
+    attrs = {
+        "internal_stamp_flag_detect": attr.bool(default = False),
+        "out": attr.output(mandatory = True),
+        "stamp": attr.int(values = [-1, 0, 1], default = -1, doc = _STAMP_DOC),
+        "substitutions": attr.string_dict(
+            doc = "Extra substitutions, potentially overriding defaults.",
+        ),
+        "template": attr.label(
+            allow_single_file = True,
+        ),
+        "_alpha_number_flag": attr.label(default = ":alpha_number"),
+        "_beta_number_flag": attr.label(default = ":beta_number"),
+        "_gen_tmpl_tool": attr.label(
+            default = Label("//bazel/version:gen_tmpl.py"),
+            allow_single_file = True,
+            executable = True,
+            cfg = "exec",
+        ),
+        "_nightly_date_flag": attr.label(default = ":nightly_date"),
+        "_pre_release_flag": attr.label(default = ":pre_release"),
+        "_rc_number_flag": attr.label(default = ":rc_number"),
+        "_release_flag": attr.label(default = ":release"),
+    },
+)
+
+# We need a macro wrapping the rule so that we can inject a select that observes
+# the `--stamp` command-line value and use that when needed.
+def expand_version_build_info(name, **kwargs):
+    expand_version_build_info_internal(
+        name = name,
+        internal_stamp_flag_detect = select({
+            "//bazel/version:internal_stamp_flag_detect": True,
+            "//conditions:default": False,
+        }),
+        **kwargs
+    )

+ 72 - 0
common/BUILD

@@ -3,6 +3,7 @@
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
+load("//bazel/version:rules.bzl", "expand_version_build_info")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -456,6 +457,77 @@ cc_library(
     ],
 )
 
+# The base version source file only uses non-stamped parts of the version
+# information so we expand it once here without any stamping.
+expand_version_build_info(
+    name = "version_cpp_gen",
+    out = "version.cpp",
+    stamp = 0,
+    template = "version.tmpl.cpp",
+)
+
+# Build a nostamp version of the stamp source, but mark its definitions as weak.
+# We'll include this in the library to satisfy definitions of library and test
+# users, but still allow binaries that want full build stamping to depend on the
+# stamp library below to override with strong, stamped definitions.
+expand_version_build_info(
+    name = "version_nostamp_cpp_gen",
+    out = "version_nostamp.cpp",
+    stamp = 0,
+    substitutions = {"MAKE_WEAK": "1"},
+    template = "version_stamp.tmpl.cpp",
+)
+
+# Provides APIs for accessing Carbon version information.
+#
+# These provide full access to the major, minor, and patch version. It also
+# provides an API for querying version strings that may contain detailed build
+# information such as the commit SHA.
+#
+# By default, this provides the API and an *unstamped* implementations of
+# version strings. As a consequence, depending on this library doesn't introduce
+# any dependency on the commit SHA or loss of build caching.
+#
+# Targets that want full build info stamping in the data produced by these APIs
+# should additionally depend on `:version_stamp` below -- the data these APIs
+# return will be overridden in any binaries depending on that rule with the
+# fully stamped details.
+cc_library(
+    name = "version",
+    srcs = [
+        "version.cpp",
+        "version_nostamp.cpp",
+    ],
+    hdrs = ["version.h"],
+    deps = [
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+# Generate the fully stamped sourcefile if stamping is enabled in the build.
+expand_version_build_info(
+    name = "version_stamp_cpp_gen",
+    out = "version_stamp.cpp",
+    template = "version_stamp.tmpl.cpp",
+)
+
+# Depend on this library to enable fully-stamped build information in the
+# version API provided by `:version`. This doesn't provide the API, it injects
+# an override of stamped versions of the data.
+#
+# Note that depending on this will significantly reduce build caching with
+# `--stamp` builds. It should be used sparingly, typically in user-facing
+# binaries or systems that need to render a maximally detailed version string
+# with build information stamped into it.
+cc_library(
+    name = "version_stamp",
+    srcs = ["version_stamp.cpp"],
+    deps = [
+        ":version",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
 cc_library(
     name = "vlog",
     srcs = ["vlog_internal.h"],

+ 27 - 0
common/version.h

@@ -0,0 +1,27 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef CARBON_COMMON_VERSION_H_
+#define CARBON_COMMON_VERSION_H_
+
+#include "llvm/ADT/StringRef.h"
+
+namespace Carbon {
+
+struct Version {
+  static const int Major;
+  static const int Minor;
+  static const int Patch;
+
+  static const llvm::StringLiteral String;
+
+  // A dedicated version information string to use in the toolchain as its
+  // command line rendered version. Composed centrally so it can be composed at
+  // compile time with potentially build info stamped components.
+  static const llvm::StringLiteral ToolchainInfo;
+};
+
+}  // namespace Carbon
+
+#endif  // CARBON_COMMON_VERSION_H_

+ 51 - 0
common/version.tmpl.cpp

@@ -0,0 +1,51 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "common/version.h"
+
+#include <string_view>
+
+namespace Carbon {
+
+// A simplistic string-to-integer routine that is consteval for compile-time
+// extracting specific components of the version from the string form. We use
+// `std::string_view` for its broader `constexpr` API.
+static consteval auto ToInt(std::string_view str) -> int {
+  int result = 0;
+  while (true) {
+    result += str.front() - '0';
+    str.remove_prefix(1);
+    if (str.empty()) {
+      break;
+    }
+    result *= 10;
+  }
+  return result;
+}
+
+static consteval auto MajorVersion(std::string_view str) -> int {
+  return ToInt(str.substr(0, str.find('.')));
+}
+
+static consteval auto MinorVersion(std::string_view str) -> int {
+  str.remove_prefix(str.find('.') + 1);
+  return ToInt(str.substr(0, str.find('.')));
+}
+
+static consteval auto PatchVersion(std::string_view str) -> int {
+  str.remove_prefix(str.find('.') + 1);
+  str.remove_prefix(str.find('.') + 1);
+  // Note that searching for `-` may find the end of the string if there is no
+  // pre-release component, but that produces the correct result here.
+  return ToInt(str.substr(0, str.find('-')));
+}
+
+// The major, minor, and patch versions are always provided and stable. They
+// don't depend on build stamping or introduce caching issues. Provide normal
+// strong definitions.
+constexpr int Version::Major = MajorVersion("$VERSION");
+constexpr int Version::Minor = MinorVersion("$VERSION");
+constexpr int Version::Patch = PatchVersion("$VERSION");
+
+}  // namespace Carbon

+ 25 - 0
common/version_stamp.tmpl.cpp

@@ -0,0 +1,25 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "common/version.h"
+
+namespace Carbon {
+
+#pragma clang attribute push
+// If requested, make the contents of this file weak.
+#if $MAKE_WEAK
+#pragma clang attribute(__attribute__((weak)), \
+                        apply_to = any(function, variable))
+#endif
+
+constexpr llvm::StringLiteral Version::String =
+    "$VERSION+$GIT_COMMIT_SHA$GIT_DIRTY_SUFFIX";
+
+constexpr llvm::StringLiteral Version::ToolchainInfo = R"""(
+Carbon Language toolchain version: $VERSION+$GIT_COMMIT_SHA$GIT_DIRTY_SUFFIX
+)""";
+
+#pragma clang attribute pop
+
+}  // namespace Carbon

+ 5 - 3
scripts/fix_cc_deps.py

@@ -79,12 +79,14 @@ EXTERNAL_REPOS: Dict[str, ExternalRepo] = {
     ),
 }
 
-# TODO: proto rules are aspect-based and their generated files don't show up in
-# `bazel query` output.
+# TODO: proto rules and template expansions are aspect-based and their generated
+# files don't show up in `bazel query` output.
 # Try using `bazel cquery --output=starlark` to print `target.files`.
 # For protobuf, need to add support for `alias` rule kind.
 IGNORE_HEADER_REGEX = re.compile("^(.*\\.pb\\.h)$")
-IGNORE_SOURCE_FILE_REGEX = re.compile("^third_party/clangd")
+IGNORE_SOURCE_FILE_REGEX = re.compile(
+    "^(third_party/clangd.*|common/version.*\\.cpp)$"
+)
 
 
 class Rule(NamedTuple):

+ 37 - 0
scripts/workspace_status.py

@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+
+"""Bazel `--workspace_status_command` script.
+
+This script is designed to be used in Bazel`s `--workspace_status_command` and
+generate any desirable status artifacts.
+"""
+
+__copyright__ = """
+Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+Exceptions. See /LICENSE for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+"""
+
+import subprocess
+
+
+def git_commit_sha() -> str:
+    return subprocess.check_output(
+        ["git", "rev-parse", "--short", "HEAD"], encoding="utf-8"
+    ).strip()
+
+
+def git_dirty_suffix() -> str:
+    status = subprocess.check_output(
+        ["git", "status", "--porcelain"], encoding="utf-8"
+    ).strip()
+    return ".dirty" if len(status) > 0 else ""
+
+
+def main() -> None:
+    print("STABLE_GIT_COMMIT_SHA " + git_commit_sha())
+    print("STABLE_GIT_DIRTY_SUFFIX " + git_dirty_suffix())
+
+
+if __name__ == "__main__":
+    main()

+ 2 - 0
toolchain/driver/BUILD

@@ -63,6 +63,7 @@ cc_library(
     deps = [
         ":clang_runner",
         "//common:command_line",
+        "//common:version",
         "//common:vlog",
         "//toolchain/base:value_store",
         "//toolchain/check",
@@ -127,6 +128,7 @@ cc_binary(
         "//common:bazel_working_dir",
         "//common:exe_path",
         "//common:init_llvm",
+        "//common:version_stamp",
         "//toolchain/install:install_paths",
         "@llvm-project//llvm:Support",
     ],

+ 21 - 18
toolchain/driver/driver.cpp

@@ -9,6 +9,7 @@
 #include <optional>
 
 #include "common/command_line.h"
+#include "common/version.h"
 #include "common/vlog.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/ScopeExit.h"
@@ -398,24 +399,7 @@ The linked file name. The output is always a linked binary.
 };
 
 struct Driver::Options {
-  static constexpr CommandLine::CommandInfo Info = {
-      .name = "carbon",
-      // TODO: Set up more detailed version information and use that here.
-      .version = R"""(
-Carbon Language toolchain -- version 0.0.0
-)""",
-      .help = R"""(
-This is the unified Carbon Language toolchain driver. Its subcommands provide
-all of the core behavior of the toolchain, including compilation, linking, and
-developer tools. Each of these has its own subcommand, and you can pass a
-specific subcommand to the `help` subcommand to get details about its usage.
-)""",
-      .help_epilogue = R"""(
-For questions, issues, or bug reports, please use our GitHub project:
-
-  https://github.com/carbon-language/carbon-lang
-)""",
-  };
+  static const CommandLine::CommandInfo Info;
 
   enum class Subcommand : int8_t {
     Compile,
@@ -453,6 +437,25 @@ For questions, issues, or bug reports, please use our GitHub project:
   LinkOptions link_options;
 };
 
+// Note that this is not constexpr so that it can include information generated
+// in separate translation units and potentially overridden at link time in the
+// version string.
+const CommandLine::CommandInfo Driver::Options::Info = {
+    .name = "carbon",
+    .version = Version::ToolchainInfo,
+    .help = R"""(
+This is the unified Carbon Language toolchain driver. Its subcommands provide
+all of the core behavior of the toolchain, including compilation, linking, and
+developer tools. Each of these has its own subcommand, and you can pass a
+specific subcommand to the `help` subcommand to get details about its usage.
+)""",
+    .help_epilogue = R"""(
+For questions, issues, or bug reports, please use our GitHub project:
+
+  https://github.com/carbon-language/carbon-lang
+)""",
+};
+
 auto Driver::ParseArgs(llvm::ArrayRef<llvm::StringRef> args, Options& options)
     -> CommandLine::ParseResult {
   return CommandLine::Parse(

+ 15 - 5
toolchain/install/BUILD

@@ -8,6 +8,7 @@ load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test")
 load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_filegroup", "pkg_files", "pkg_mklink", "strip_prefix")
 load("@rules_pkg//pkg:tar.bzl", "pkg_tar")
 load("@rules_pkg//pkg:zip.bzl", "pkg_zip")
+load("pkg_naming.bzl", "pkg_naming_variables")
 load("symlink_filegroup.bzl", "symlink_filegroup")
 
 package(default_visibility = ["//visibility:public"])
@@ -185,23 +186,32 @@ pkg_filegroup(
     ],
 )
 
+pkg_naming_variables(
+    name = "packaging_variables",
+)
+
 # TODO: We should add support for injecting a version string into both the
 # output filename and the package directory name.
 pkg_tar(
-    name = "carbon_toolchain.tar.bz2.rule",
+    name = "carbon_toolchain_tar_rule",
     srcs = [":packaging_files"],
-    out = "carbon_toolchain.tar.bz2",
     extension = "tar.bz2",
-    package_dir = "carbon_toolchain",
+    package_dir = "carbon_toolchain-$(version)",
+    package_file_name = "carbon_toolchain-$(version).tar.bz2",
+    package_variables = ":packaging_variables",
+    stamp = -1,  # Allow `--stamp` builds to produce file timestamps.
     tags = ["manual"],  # Slow, exclude from wildcard builds.
 )
 
 # TODO: We should add support for injecting a version string into both the
 # output filename and the package directory name.
 pkg_zip(
-    name = "carbon_toolchain.zip.rule",
+    name = "carbon_toolchain_zip_rule",
     srcs = [":packaging_files"],
     out = "carbon_toolchain.zip",
-    package_dir = "carbon_toolchain",
+    package_dir = "carbon_toolchain-$(version)",
+    package_file_name = "carbon_toolchain-$(version).zip",
+    package_variables = ":packaging_variables",
+    stamp = -1,  # Allow `--stamp` builds to produce file timestamps.
     tags = ["manual"],  # Slow, exclude from wildcard builds.
 )

+ 24 - 0
toolchain/install/pkg_naming.bzl

@@ -0,0 +1,24 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""Rule to create variables for package naming."""
+
+load("@rules_pkg//pkg:providers.bzl", "PackageVariablesInfo")
+load("//bazel/version:compute_version.bzl", "VERSION_ATTRS", "compute_version")
+
+def _pkg_naming_variables_impl(ctx):
+    # TODO: Add support for digging the target CPU out of the toolchain here,
+    # remapping it to a more canonical name, and add that to the variables. The
+    # Bazel target CPU is already directly available, but it isn't likely
+    # canonical.
+    # TODO: Include the target OS as well as the target CPU. This likely needs
+    # similar re-mapping as the CPU does.
+    return PackageVariablesInfo(values = {
+        "version": compute_version(ctx),
+    })
+
+pkg_naming_variables = rule(
+    implementation = _pkg_naming_variables_impl,
+    attrs = VERSION_ATTRS,
+)

+ 22 - 0
version_base.bzl

@@ -0,0 +1,22 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+"""The base version of the Carbon repository.
+
+Carbon's version is defined here for export to various layers
+where it is needed. This is not the currently *released* version, but the
+version under *active development*.
+
+The current active development version is 0.0.0 -- we haven't yet made enough
+progress towards our 0.1 milestone to meaningfully bump the version to that even
+to describe the development activity. We also never plan to make even a
+non-development pre-release of 0.0.0, only nightly development pre-releases are
+expected.
+
+For more details on Carbon's versioning, see:
+  https://semver.org/
+  TODO: Add Carbon-specific link.
+"""
+
+version_base = "0.0.0"