Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add customization for git ignore file #100

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ refresh_compile_commands(
name = "refresh_all",
)


########################################
# Implementation:
# If you are looking into the implementation, start with the overview in ImplementationReadme.md.

exports_files(["refresh.template.py"]) # For implicit use by refresh_compile_commands.
exports_files(["refresh.template.py"]) # For implicit use by refresh_compile_commands.
5 changes: 3 additions & 2 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# This file existed originally to enable quick local development via local_repository.
# See ./ImplementationReadme.md for details on local development.
# Why? local_repository didn't work without a WORKSPACE, and new_local_repository required overwriting the BUILD file (as of Bazel 5.0).
# See ./ImplementationReadme.md for details on local development.
# Why? local_repository didn't work without a WORKSPACE, and new_local_repository required overwriting the BUILD file (as of Bazel 5.0).

workspace(name = "hedron_compile_commands")

load("@hedron_compile_commands//:workspace_setup.bzl", "hedron_compile_commands_setup")

hedron_compile_commands_setup()
39 changes: 39 additions & 0 deletions private/util.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""
Utility functions for reading/writing workspace setup information.
"""

def write_dict_entries(d, indent_level = 1):
"""Write the entries of dictionary to a string."""
return "\n".join([
"{indent}\"{key}\": \"{value}\",".format(
indent = indent_level * 4 * " ",
key = key,
value = value,
)
for key, value in d.items()
])

def _write_install_config_impl(rctx):
rctx.file("WORKSPACE.bazel", executable = False)
rctx.file("BUILD.bazel", executable = False)

entries = write_dict_entries(rctx.attr.install_config)

rctx.file(
"config.bzl",
content = """
INSTALL_CONFIG = {{
{entries}
}}
""".format(entries = entries),
executable = False,
)

write_install_config = repository_rule(
implementation = _write_install_config_impl,
attrs = {
"install_config": attr.string_dict(
doc = "Installation configuration parameters (e.g. gitignore file).",
),
},
)
23 changes: 15 additions & 8 deletions refresh.template.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#!/usr/bin/env python
"""
As a template, this file helps implement the refresh_compile_commands rule and is not part of the user interface. See ImplementationReadme.md for top-level context -- or refresh_compile_commands.bzl for narrower context.

Expand Down Expand Up @@ -971,11 +972,11 @@ def _ensure_external_workspaces_link_exists():
It's a win/win: It's easier for you to browse the code you use, and it eliminates whole categories of edge cases for build tooling.""")


def _ensure_gitignore_entries_exist():
def _ensure_gitignore_entries_exist(ignore_file):
"""Ensure `/compile_commands.json`, `/external`, and other useful entries are `.gitignore`'d if it looks like git is used."""

# Do nothing if we aren't within a git repository and there is no `.gitignore` file. We still add to the .gitignore file if it exists, even if we aren't in a git repository (perhaps because git was temporarily uninstalled).
if not os.path.isfile('.gitignore'): # Check .gitignore first as a fast path
if not os.path.isfile(ignore_file): # Check .gitignore first as a fast path
# Silently check if we're (nested) within a git repository. It isn't sufficient to check for the presence of a `.git` directory, in case the bazel workspace lives underneath the top-level git repository.
# See https://stackoverflow.com/questions/2180270/check-if-current-directory-is-a-git-repository for a few ways to test.
is_git_repository = subprocess.run('git rev-parse --git-dir',
Expand All @@ -993,7 +994,7 @@ def _ensure_gitignore_entries_exist():
]

# Create `.gitignore` if it doesn't exist (and don't truncate if it does) and open it for appending/updating.
with open('.gitignore', 'a+') as gitignore:
with open(ignore_file, 'a+') as gitignore:
gitignore.seek(0) # Files opened in `a` mode seek to the end, so we reset to the beginning so we can read.
# Recall that trailing spaces, when escaped with `\`, are meaningful to git. However, none of the entries for which we're searching end with literal spaces, so we can safely trim all trailing whitespace. That said, we can't rewrite these stripped lines to the file, in case an existing entry is e.g. `/foo\ `, matching the file "foo " (with a trailing space), whereas the entry `/foo\` does not match the file `"foo "`.
lines = [l.rstrip() for l in gitignore]
Expand All @@ -1010,7 +1011,7 @@ def _ensure_gitignore_entries_exist():
for pattern, comment in missing:
print(comment, file=gitignore)
print(pattern, file=gitignore)
log_success(">>> Automatically added entries to .gitignore to avoid problems.")
log_success(">>> Automatically added entries to {} to avoid problems.".format(ignore_file))


def _ensure_cwd_is_workspace_root():
Expand All @@ -1027,16 +1028,22 @@ def _ensure_cwd_is_workspace_root():


if __name__ == '__main__':
_ensure_cwd_is_workspace_root()
_ensure_gitignore_entries_exist()
_ensure_external_workspaces_link_exists()

target_flag_pairs = [
# Begin: template filled by Bazel
{target_flag_pairs}
# End: template filled by Bazel
]

install_config = {
{compile_commands_install_config}
}

_ensure_cwd_is_workspace_root()
_ensure_gitignore_entries_exist(
ignore_file = install_config.get("ignore_file", ".gitignore")
)
_ensure_external_workspaces_link_exists()

compile_command_entries = []
for (target, flags) in target_flag_pairs:
compile_command_entries.extend(_get_commands(target, flags))
Expand Down
12 changes: 9 additions & 3 deletions refresh_compile_commands.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,12 @@ refresh_compile_commands(
```
"""


########################################
# Implementation

load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
load("@hedron_compile_commands_install_config//:config.bzl", _install_config = "INSTALL_CONFIG")
load("//:private/util.bzl", _write_dict_entries = "write_dict_entries")

def refresh_compile_commands(
name,
Expand All @@ -68,7 +69,7 @@ def refresh_compile_commands(
# In Python, `type(x) == y` is an antipattern, but [Starlark doesn't support inheritance](https://bazel.build/rules/language), so `isinstance` doesn't exist, and this is the correct way to switch on type.
if not targets: # Default to all targets in main workspace
targets = {"@//...": ""}
elif type(targets) == "select": # Allow select: https://bazel.build/reference/be/functions#select
elif type(targets) == "select": # Allow select: https://bazel.build/reference/be/functions#select
# Pass select() to _expand_template to make it work
# see https://bazel.build/docs/configurable-attributes#faq-select-macro
pass
Expand All @@ -85,6 +86,7 @@ def refresh_compile_commands(
def _expand_template_impl(ctx):
"""Inject targets of interest into refresh.template.py, and set it up to be run."""
script = ctx.actions.declare_file(ctx.attr.name)

ctx.actions.expand_template(
output = script,
is_executable = True,
Expand All @@ -95,6 +97,10 @@ def _expand_template_impl(ctx):
" {windows_default_include_paths}": "\n".join([" %r," % path for path in find_cpp_toolchain(ctx).built_in_include_directories]), # find_cpp_toolchain is from https://docs.bazel.build/versions/main/integrating-with-rules-cc.html
"{exclude_headers}": repr(ctx.attr.exclude_headers),
"{exclude_external_sources}": repr(ctx.attr.exclude_external_sources),
"{compile_commands_install_config}": _write_dict_entries(
_install_config,
indent_level = 2,
),
},
)
return DefaultInfo(files = depset([script]))
Expand All @@ -103,7 +109,7 @@ _expand_template = rule(
attrs = {
"labels_to_flags": attr.string_dict(mandatory = True), # string keys instead of label_keyed because Bazel doesn't support parsing wildcard target patterns (..., *, :all) in BUILD attributes.
"exclude_external_sources": attr.bool(default = False),
"exclude_headers": attr.string(values = ["all", "external", ""]), # "" needed only for compatibility with Bazel < 3.6.0
"exclude_headers": attr.string(values = ["all", "external", ""]), # "" needed only for compatibility with Bazel < 3.6.0
"_script_template": attr.label(allow_single_file = True, default = "refresh.template.py"),
"_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"), # For Windows INCLUDE. If this were eliminated, for example by the resolution of https://github.com/clangd/clangd/issues/123, we'd be able to just use a macro and skylib's expand_template rule: https://github.com/bazelbuild/bazel-skylib/pull/330
},
Expand Down
10 changes: 6 additions & 4 deletions workspace_setup.bzl
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Do not change the filename; it is part of the user interface.
load("//:private/util.bzl", _write_install_config = "write_install_config")

def hedron_compile_commands_setup():
def hedron_compile_commands_setup(install_config = {}):
"""Set up a WORKSPACE to have hedron_compile_commands used within it."""

# Unified setup for users' WORKSPACES and this workspace when used standalone.
# See invocations in:
# README.md (for users)
# WORKSPACE (for working on this repo standalone)

# Currently nothing to do -> no-op.
# So why is this even here? Enables future expansion (e.g to add transitive dependencies) without changing the user interface.
pass
_write_install_config(
name = "hedron_compile_commands_install_config",
install_config = install_config,
)