Android Build Tools

This commit is contained in:
Isuru Samarathunga
2025-10-16 00:43:42 +05:30
parent f742dcfaff
commit 160bf65a1f
5549 changed files with 1752060 additions and 0 deletions

View File

@ -0,0 +1,65 @@
# Please add "source /path/to/bash-autocomplete.sh" to your .bashrc to use this.
_clang_filedir()
{
# _filedir function provided by recent versions of bash-completion package is
# better than "compgen -f" because the former honors spaces in pathnames while
# the latter doesn't. So we use compgen only when _filedir is not provided.
_filedir 2> /dev/null || COMPREPLY=( $( compgen -f ) )
}
_clang()
{
local cur prev words cword arg flags w1 w2
# If latest bash-completion is not supported just initialize COMPREPLY and
# initialize variables by setting manually.
_init_completion -n 2> /dev/null
if [[ "$?" != 0 ]]; then
COMPREPLY=()
cword=$COMP_CWORD
cur="${COMP_WORDS[$cword]}"
fi
w1="${COMP_WORDS[$cword - 1]}"
if [[ $cword > 1 ]]; then
w2="${COMP_WORDS[$cword - 2]}"
fi
# Pass all the current command-line flags to clang, so that clang can handle
# these internally.
# '=' is separated differently by bash, so we have to concat them without ','
for i in `seq 1 $cword`; do
if [[ $i == $cword || "${COMP_WORDS[$(($i+1))]}" == '=' ]]; then
arg="$arg${COMP_WORDS[$i]}"
else
arg="$arg${COMP_WORDS[$i]},"
fi
done
# expand ~ to $HOME
eval local path=${COMP_WORDS[0]}
# Use $'\t' so that bash expands the \t for older versions of sed.
flags=$( "$path" --autocomplete="$arg" 2>/dev/null | sed -e $'s/\t.*//' )
# If clang is old that it does not support --autocomplete,
# fall back to the filename completion.
if [[ "$?" != 0 ]]; then
_clang_filedir
return
fi
# When clang does not emit any possible autocompletion, or user pushed tab after " ",
# just autocomplete files.
if [[ "$flags" == "$(echo -e '\n')" ]]; then
# If -foo=<tab> and there was no possible values, autocomplete files.
[[ "$cur" == '=' || "$cur" == -*= ]] && cur=""
_clang_filedir
elif [[ "$cur" == '=' ]]; then
COMPREPLY=( $( compgen -W "$flags" -- "") )
else
# Bash automatically appends a space after '=' by default.
# Disable it so that it works nicely for options in the form of -foo=bar.
[[ "${flags: -1}" == '=' ]] && compopt -o nospace 2> /dev/null
COMPREPLY=( $( compgen -W "$flags" -- "$cur" ) )
fi
}
complete -F _clang clang

View File

@ -0,0 +1,969 @@
.dark-primary-color { background: #1976D2; }
.default-primary-color { background: #2196F3; }
.light-primary-color { background: #BBDEFB; }
.text-primary-color { color: #FFFFFF; }
.accent-color { background: #00BCD4; }
.primary-text-color { color: #212121; }
.secondary-text-color { color: #727272; }
.divider-color { border-color: #B6B6B6; }
/* for layout */
html,
body {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
overflow: hidden;
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
header {
flex: 0 0 50px;
display: flex;
flex-direction: row;
align-items: center;
padding-left: 30px;
}
header ol {
list-style: none;
margin: 0;
padding: 0;
}
header ol li {
display: inline;
}
header form {
display: flex;
flex: 1;
justify-content: flex-end;
padding-right: 30px;
}
header#header-search-sidebar {
height: 50px;
margin-bottom: 25px;
}
footer {
flex: 0 0 16px;
text-align: center;
padding: 16px 20px;
}
main {
flex: 1;
display: flex;
flex-direction: row;
padding: 20px;
min-height: 0;
}
.sidebar-offcanvas-left {
flex: 0 1 230px;
overflow-y: scroll;
padding: 20px 0 15px 30px;
margin: 5px 20px 0 0;
visibility: visible; /* shown by Javascript after scroll position restore */
}
::-webkit-scrollbar-button{ display: none; height: 13px; border-radius: 0px; background-color: #AAA; }
::-webkit-scrollbar-button:hover{ background-color: #AAA; }
::-webkit-scrollbar-thumb{ background-color: #CCC; }
::-webkit-scrollbar-thumb:hover{ background-color: #CCC; }
::-webkit-scrollbar{ width: 4px; }
/* ::-webkit-overflow-scrolling: touch; */
.main-content::-webkit-scrollbar{ width: 8px; }
.main-content {
flex: 1;
overflow-y: scroll;
padding: 10px 20px 0 20px;
visibility: visible; /* shown by Javascript after scroll position restore */
}
.sidebar-offcanvas-right {
flex: 0 1 12em;
overflow-y: scroll;
padding: 20px 15px 15px 15px;
margin-top: 5px;
margin-right: 20px;
visibility: visible; /* shown by Javascript after scroll position restore */
}
/* end for layout */
body {
-webkit-text-size-adjust: 100%;
overflow-x: hidden;
font-family: Roboto, sans-serif;
font-size: 16px;
line-height: 1.42857143;
color: #111111;
background-color: #fff;
}
/* some of this is to reset bootstrap */
nav.navbar {
background-color: inherit;
min-height: 50px;
border: 0;
}
@media (max-width: 768px) {
.hidden-xs {
display: none !important;
}
}
@media (min-width: 769px) {
.hidden-l {
display: none !important;
}
}
nav.navbar .row {
padding-top: 8px;
}
nav .container {
white-space: nowrap;
}
header {
background-color: #eeeeee;
box-shadow: 0 3px 5px rgba(0,0,0,0.1);
}
header#project-title {
background-color: #fff;
font-size: 200%;
padding-top: 0.25em;
padding-bottom: 0.25em;
/* padding: 0em; */
}
header.header-fixed nav.navbar-fixed-top {
box-shadow: 0 3px 5px rgba(0,0,0,0.1);
}
header.container-fluid {
padding: 0;
}
header .masthead {
padding-top: 64px;
}
header .contents {
padding: 0;
}
@media screen and (max-width:768px) {
header .contents {
padding-left: 15px;
padding-right: 15px;
}
}
a {
text-decoration: none;
}
.body {
margin-top: 90px;
}
section {
margin-bottom: 36px;
}
dl {
margin: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: Roboto, sans-serif;
font-weight: 400;
margin-top: 1.5em;
color: #111111;
}
h1.title {
overflow: hidden;
text-overflow: ellipsis;
}
h1 {
font-size: 37px;
margin-top: 0;
margin-bottom: 0.67em;
}
h2 {
font-size: 28px;
}
h5 {
font-size: 16px;
}
.subtitle {
font-size: 17px;
min-height: 1.4em;
}
.title-description .subtitle {
white-space: nowrap;
overflow-x: hidden;
text-overflow: ellipsis;
}
p {
margin-bottom: 1em;
margin-top: 0;
}
a {
color: #0175C2;
}
a:hover {
color: #13B9FD;
}
pre.prettyprint {
font-family: 'Source Code Pro', Menlo, monospace;
color: black;
border-radius: 0;
font-size: 15px;
word-wrap: normal;
line-height: 1.4;
border: 0;
margin: 16px 0 16px 0;
padding: 8px;
}
pre code {
white-space: pre;
word-wrap: initial;
font-size: 100%
}
.fixed {
white-space: pre;
}
pre {
border: 1px solid #ddd;
background-color: #eee;
font-size: 14px;
}
code {
font-family: 'Source Code Pro', Menlo, monospace;
/* overriding bootstrap */
color: inherit;
padding: 0.2em 0.4em;
font-size: 85%;
background-color: rgba(27,31,35,0.05);
border-radius: 3px;
}
@media(max-width: 768px) {
nav .container {
width: 100%
}
h1 {
font-size: 24px;
}
pre {
margin: 16px 0;
}
}
@media (min-width: 768px) {
ul.subnav li {
font-size: 17px;
}
}
header h1 {
font-weight: 400;
margin-bottom: 16px;
}
header a,
header p,
header li {
color: #111111;
}
header a:hover {
color: #0175C2;
}
header h1 .kind {
color: #555;
}
dt {
font-weight: normal;
}
dd {
color: #212121;
margin-bottom: 1em;
margin-left: 0;
}
dd.callable, dd.constant, dd.property {
margin-bottom: 24px;
}
dd p {
overflow-x: hidden;
text-overflow: ellipsis;
margin-bottom: 0;
}
/* indents wrapped lines */
section.summary dt {
margin-left: 24px;
text-indent: -24px;
}
.dl-horizontal dd {
margin-left: initial;
}
dl.dl-horizontal dt {
font-style: normal;
text-align: left;
color: #727272;
margin-right: 20px;
width: initial;
}
dt .name {
font-weight: 500;
}
dl dt.callable .name {
float: none;
width: auto;
}
.parameter {
white-space: nowrap;
}
.type-parameter {
white-space: nowrap;
}
.multi-line-signature .type-parameter .parameter {
margin-left: 0px;
display: unset;
}
.signature {
color: #727272;
}
.signature a {
/* 50% mix of default-primary-color and primary-text-color. */
color: #4674a2;
}
.optional {
font-style: italic;
}
.undocumented {
font-style: italic;
}
.is-const {
font-style: italic;
}
.deprecated {
text-decoration: line-through;
}
.category.linked {
font-weight: bold;
opacity: 1;
}
/* Colors for category based on categoryOrder in dartdoc_options.config. */
.category.cp-0 {
background-color: #54b7c4
}
.category.cp-1 {
background-color: #54c47f
}
.category.cp-2 {
background-color: #c4c254
}
.category.cp-3 {
background-color: #c49f54
}
.category.cp-4 {
background-color: #c45465
}
.category.cp-5 {
background-color: #c454c4
}
.category a {
color: white;
}
.category {
padding: 2px 4px;
font-size: 12px;
border-radius: 4px;
background-color: #999;
text-transform: uppercase;
color: white;
opacity: .5;
}
h1 .category {
vertical-align: middle;
}
.source-link {
padding: 18px 4px;
vertical-align: middle;
}
.source-link .material-icons {
font-size: 18px;
}
@media (max-width: 768px) {
.source-link {
padding: 7px 2px;
font-size: 10px;
}
}
#external-links {
float: right;
}
.btn-group {
position: relative;
display: inline-flex;
vertical-align: middle;
}
p.firstline {
font-weight: bold;
}
footer {
color: #fff;
background-color: #111111;
width: 100%;
}
footer p {
margin: 0;
}
footer .no-break {
white-space: nowrap;
}
footer .container,
footer .container-fluid {
padding-left: 0;
padding-right: 0;
}
footer a, footer a:hover {
color: #fff;
}
.markdown.desc {
max-width: 700px;
}
.markdown h1 {
font-size: 24px;
margin-bottom: 8px;
}
.markdown h2 {
font-size: 20px;
margin-top: 24px;
margin-bottom: 8px;
}
.markdown h3 {
font-size: 18px;
margin-bottom: 8px;
}
.markdown h4 {
font-size: 16px;
margin-bottom: 0;
}
.markdown li p {
margin: 0;
}
.gt-separated {
list-style: none;
padding: 0;
margin: 0;
}
.gt-separated li {
display: inline-block;
}
.gt-separated li:before {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='%23DDDDDD' d='M6.7,4L5.7,4.9L8.8,8l-3.1,3.1L6.7,12l4-4L6.7,4z'/></svg>");
background-position: center;
content: "\00a0";
margin: 0 6px 0 4px;
padding: 0 3px 0 0;
}
.gt-separated.dark li:before {
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path fill='%23727272' d='M6.7,4L5.7,4.9L8.8,8l-3.1,3.1L6.7,12l4-4L6.7,4z'/></svg>");
}
.gt-separated li:first-child:before {
background-image: none;
content: "";
margin: 0;
}
/* The slug line under a declaration for things like "const", "read-only", etc. */
.features {
font-style: italic;
color: #727272;
}
.multi-line-signature {
font-size: 17px;
color: #727272;
}
.multi-line-signature .parameter {
margin-left: 24px;
display: block;
}
.breadcrumbs {
padding: 0;
margin: 8px 0 8px 0;
white-space: nowrap;
line-height: 1;
}
@media screen and (min-width: 768px) {
nav ol.breadcrumbs {
float: left;
}
}
@media screen and (max-width: 768px) {
.breadcrumbs {
margin: 0 0 24px 0;
overflow-x: hidden;
}
}
.self-crumb {
color: #555;
}
.self-name {
color: #555;
display: none;
}
.annotation-list {
list-style: none;
padding: 0;
display: inline;
}
.comma-separated {
list-style: none;
padding: 0;
display: inline;
}
.comma-separated li {
display: inline;
}
.comma-separated li:after {
content: ", ";
}
.comma-separated li:last-child:after {
content: "";
}
.end-with-period li:last-child:after {
content: ".";
}
.container > section:first-child {
border: 0;
}
.constructor-modifier {
font-style: italic;
}
section.multi-line-signature div.parameters {
margin-left: 24px;
}
/* subnav styles */
ul.subnav {
overflow: auto;
white-space: nowrap;
padding-left: 0;
min-height: 25px;
}
ul.subnav::-webkit-scrollbar {
display: none;
}
ul.subnav li {
display: inline-block;
text-transform: uppercase;
}
ul.subnav li a {
color: #111;
}
ul.subnav li {
margin-right: 24px;
}
ul.subnav li:last-of-type {
margin-right: 0;
}
@media(max-width: 768px) {
ul.subnav li {
margin-right: 16px;
}
}
/* sidebar styles */
.sidebar ol {
list-style: none;
line-height: 22px;
margin-top: 0;
margin-bottom: 0;
padding: 0 0 15px 0;
}
.sidebar h5 a,
.sidebar h5 a:hover {
color: #727272;
}
.sidebar h5,
.sidebar ol li {
text-overflow: ellipsis;
overflow: hidden;
padding: 3px 0;
}
.sidebar h5 {
color: #727272;
font-size: 18px;
margin: 0 0 25px 0;
padding-top: 0;
}
.sidebar ol li.section-title {
font-size: 18px;
font-weight: normal;
text-transform: uppercase;
padding-top: 25px;
}
.sidebar ol li.section-subtitle a {
color: inherit;
}
.sidebar ol li.section-subtitle {
font-weight: 400;
text-transform: uppercase;
}
.sidebar ol li.section-subitem {
margin-left: 12px;
}
.sidebar ol li:first-child {
padding-top: 0;
margin-top: 0;
}
button {
padding: 0;
}
#sidenav-left-toggle {
display: none;
vertical-align: text-bottom;
padding: 0;
}
/* left-nav disappears, and can transition in from the left */
@media screen and (max-width:768px) {
#sidenav-left-toggle {
display: inline;
background: no-repeat url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><path fill='%23111' d='M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z'/></svg>");
background-position: center;
width: 24px;
height: 24px;
border: none;
margin-right: 24px;
}
#overlay-under-drawer.active {
opacity: 0.4;
height: 100%;
z-index: 1999;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: black;
display: block;
}
.sidebar-offcanvas-left {
left: -100%;
position: fixed;
-webkit-transition:all .25s ease-out;
-o-transition:all .25s ease-out;
transition:all .25s ease-out;
z-index: 2000;
top: 0;
width: 280px; /* works all the way down to an iphone 4 */
height: 90%;
background-color: white;
overflow-y: scroll; /* TODO: how to hide scroll bars? */
padding: 10px;
margin: 10px 10px;
box-shadow: 5px 5px 5px 5px #444444;
visibility: hidden; /* shown by Javascript after scroll position restore */
}
ol#sidebar-nav {
font-size: 18px;
white-space: pre-line;
}
.sidebar-offcanvas-left.active {
left: 0; /* this animates our drawer into the page */
}
.self-name {
display: inline-block;
}
}
.sidebar-offcanvas-left h5 {
margin-bottom: 10px;
}
.sidebar-offcanvas-left h5:last-of-type {
border: 0;
margin-bottom: 25px;
}
/* the right nav disappears out of view when the window shrinks */
@media screen and (max-width: 992px) {
.sidebar-offcanvas-right {
display: none;
}
}
#overlay-under-drawer {
display: none;
}
/* find-as-you-type search box */
/* override bootstrap defaults */
.form-control {
border-radius: 0;
border: 0;
}
@media screen and (max-width: 768px) {
form.search {
display: none;
}
}
.typeahead,
.tt-query,
.tt-hint {
width: 200px;
height: 20px;
padding: 2px 7px 1px 7px;
line-height: 20px;
outline: none;
}
.typeahead {
background-color: #fff;
border-radius: 2px;
}
.tt-query {
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.tt-hint {
color: #999
}
.navbar-right .tt-menu {
right:0;
left: inherit !important;
width: 422px;
max-height: 250px;
overflow-y: scroll;
}
.tt-menu {
font-size: 14px;
margin: 0;
padding: 8px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2);
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2);
box-shadow: 0 5px 10px rgba(0,0,0,.2);
}
.tt-suggestion {
padding: 3px 20px;
color: #212121;
}
.tt-suggestion:hover {
cursor: pointer;
color: #fff;
background-color: #0097cf;
}
.tt-suggestion:hover .search-from-lib {
color: #ddd;
}
.tt-suggestion.tt-cursor {
color: #fff;
background-color: #0097cf;
}
.tt-suggestion.tt-cursor .search-from-lib {
color: #ddd;
}
.tt-suggestion p {
margin: 0;
}
.search-from-lib {
font-style: italic;
color: gray;
}
#search-box {
background-color: #ffffff;
}
.search-body {
border: 1px solid #7f7f7f;
max-width: 400px;
box-shadow: 3px 3px 5px rgba(0,0,0,0.1);
}
section#setter {
border-top: 1px solid #ddd;
padding-top: 36px;
}
li.inherited a {
opacity: 0.65;
font-style: italic;
}
#instance-methods dt.inherited .name,
#instance-properties dt.inherited .name,
#operators dt.inherited .name {
font-weight: 300;
font-style: italic;
}
#instance-methods dt.inherited .signature,
#instance-properties dt.inherited .signature,
#operators dt.inherited .signature {
font-weight: 300;
}
@media print {
.subnav, .sidebar {
display:none;
}
a[href]:after {
content:"" !important;
}
}

View File

@ -0,0 +1,27 @@
-- In this file, change "/path/to/" to the path where you installed clang-format
-- and save it to ~/Library/Application Support/BBEdit/Scripts. You can then
-- select the script from the Script menu and clang-format will format the
-- selection. Note that you can rename the menu item by renaming the script, and
-- can assign the menu item a keyboard shortcut in the BBEdit preferences, under
-- Menus & Shortcuts.
on urlToPOSIXPath(theURL)
return do shell script "python -c \"import urllib, urlparse, sys; print urllib.unquote(urlparse.urlparse(sys.argv[1])[2])\" " & quoted form of theURL
end urlToPOSIXPath
tell application "BBEdit"
set selectionOffset to characterOffset of selection
set selectionLength to length of selection
set fileURL to URL of text document 1
end tell
set filePath to urlToPOSIXPath(fileURL)
set newContents to do shell script "/path/to/clang-format -offset=" & selectionOffset & " -length=" & selectionLength & " " & quoted form of filePath
tell application "BBEdit"
-- "set contents of text document 1 to newContents" scrolls to the bottom while
-- replacing a selection flashes a bit but doesn't affect the scroll position.
set currentLength to length of contents of text document 1
select characters 1 thru currentLength of text document 1
set text of selection to newContents
select characters selectionOffset thru (selectionOffset + selectionLength - 1) of text document 1
end tell

View File

@ -0,0 +1,193 @@
#!/usr/bin/env python3
#
# ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===------------------------------------------------------------------------===#
"""
This script reads input from a unified diff and reformats all the changed
lines. This is useful to reformat all the lines touched by a specific patch.
Example usage for git/svn users:
git diff -U0 --no-color --relative HEAD^ | {clang_format_diff} -p1 -i
svn diff --diff-cmd=diff -x-U0 | {clang_format_diff} -i
It should be noted that the filename contained in the diff is used unmodified
to determine the source file to update. Users calling this script directly
should be careful to ensure that the path in the diff is correct relative to the
current working directory.
"""
from __future__ import absolute_import, division, print_function
import argparse
import difflib
import re
import subprocess
import sys
if sys.version_info.major >= 3:
from io import StringIO
else:
from io import BytesIO as StringIO
def main():
parser = argparse.ArgumentParser(
description=__doc__.format(clang_format_diff="%(prog)s"),
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument(
"-i",
action="store_true",
default=False,
help="apply edits to files instead of displaying a diff",
)
parser.add_argument(
"-p",
metavar="NUM",
default=0,
help="strip the smallest prefix containing P slashes",
)
parser.add_argument(
"-regex",
metavar="PATTERN",
default=None,
help="custom pattern selecting file paths to reformat "
"(case sensitive, overrides -iregex)",
)
parser.add_argument(
"-iregex",
metavar="PATTERN",
default=r".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp"
r"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|s?vh?)",
help="custom pattern selecting file paths to reformat "
"(case insensitive, overridden by -regex)",
)
parser.add_argument(
"-sort-includes",
action="store_true",
default=False,
help="let clang-format sort include blocks",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="be more verbose, ineffective without -i",
)
parser.add_argument(
"-style",
help="formatting style to apply (LLVM, GNU, Google, Chromium, "
"Microsoft, Mozilla, WebKit)",
)
parser.add_argument(
"-fallback-style",
help="The name of the predefined style used as a"
"fallback in case clang-format is invoked with"
"-style=file, but can not find the .clang-format"
"file to use.",
)
parser.add_argument(
"-binary",
default="clang-format",
help="location of binary to use for clang-format",
)
args = parser.parse_args()
# Extract changed lines for each file.
filename = None
lines_by_file = {}
for line in sys.stdin:
match = re.search(r"^\+\+\+\ (.*?/){%s}(\S*)" % args.p, line)
if match:
filename = match.group(2)
if filename is None:
continue
if args.regex is not None:
if not re.match("^%s$" % args.regex, filename):
continue
else:
if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE):
continue
match = re.search(r"^@@.*\+(\d+)(?:,(\d+))?", line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(2):
line_count = int(match.group(2))
# The input is something like
#
# @@ -1, +0,0 @@
#
# which means no lines were added.
if line_count == 0:
continue
# Also format lines range if line_count is 0 in case of deleting
# surrounding statements.
end_line = start_line
if line_count != 0:
end_line += line_count - 1
lines_by_file.setdefault(filename, []).extend(
["-lines", str(start_line) + ":" + str(end_line)]
)
# Reformat files containing changes in place.
for filename, lines in lines_by_file.items():
if args.i and args.verbose:
print("Formatting {}".format(filename))
command = [args.binary, filename]
if args.i:
command.append("-i")
if args.sort_includes:
command.append("-sort-includes")
command.extend(lines)
if args.style:
command.extend(["-style", args.style])
if args.fallback_style:
command.extend(["-fallback-style", args.fallback_style])
try:
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=None,
stdin=subprocess.PIPE,
universal_newlines=True,
)
except OSError as e:
# Give the user more context when clang-format isn't
# found/isn't executable, etc.
raise RuntimeError(
'Failed to run "%s" - %s"' % (" ".join(command), e.strerror)
)
stdout, stderr = p.communicate()
if p.returncode != 0:
sys.exit(p.returncode)
if not args.i:
with open(filename) as f:
code = f.readlines()
formatted_code = StringIO(stdout).readlines()
diff = difflib.unified_diff(
code,
formatted_code,
filename,
filename,
"(before formatting)",
"(after formatting)",
)
diff_string = "".join(diff)
if len(diff_string) > 0:
sys.stdout.write(diff_string)
sys.exit(1)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,73 @@
# This file is a minimal clang-format sublime-integration. To install:
# - Change 'binary' if clang-format is not on the path (see below).
# - Put this file into your sublime Packages directory, e.g. on Linux:
# ~/.config/sublime-text-2/Packages/User/clang-format-sublime.py
# - Add a key binding:
# { "keys": ["ctrl+shift+c"], "command": "clang_format" },
#
# With this integration you can press the bound key and clang-format will
# format the current lines and selections for all cursor positions. The lines
# or regions are extended to the next bigger syntactic entities.
#
# It operates on the current, potentially unsaved buffer and does not create
# or save any files. To revert a formatting, just undo.
from __future__ import absolute_import, division, print_function
import sublime
import sublime_plugin
import subprocess
# Change this to the full path if clang-format is not on the path.
binary = "clang-format"
# Change this to format according to other formatting styles. See the output of
# 'clang-format --help' for a list of supported styles. The default looks for
# a '.clang-format' or '_clang-format' file to indicate the style that should be
# used.
style = None
class ClangFormatCommand(sublime_plugin.TextCommand):
def run(self, edit):
encoding = self.view.encoding()
if encoding == "Undefined":
encoding = "utf-8"
regions = []
command = [binary]
if style:
command.extend(["-style", style])
for region in self.view.sel():
regions.append(region)
region_offset = min(region.a, region.b)
region_length = abs(region.b - region.a)
command.extend(
[
"-offset",
str(region_offset),
"-length",
str(region_length),
"-assume-filename",
str(self.view.file_name()),
]
)
old_viewport_position = self.view.viewport_position()
buf = self.view.substr(sublime.Region(0, self.view.size()))
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
)
output, error = p.communicate(buf.encode(encoding))
if error:
print(error)
self.view.replace(
edit, sublime.Region(0, self.view.size()), output.decode(encoding)
)
self.view.sel().clear()
for region in regions:
self.view.sel().add(region)
# FIXME: Without the 10ms delay, the viewport sometimes jumps.
sublime.set_timeout(
lambda: self.view.set_viewport_position(old_viewport_position, False), 10
)

View File

@ -0,0 +1,220 @@
;;; clang-format.el --- Format code using clang-format -*- lexical-binding: t; -*-
;; Keywords: tools, c
;; Package-Requires: ((cl-lib "0.3"))
;; SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
;;; Commentary:
;; This package allows to filter code through clang-format to fix its formatting.
;; clang-format is a tool that formats C/C++/Obj-C code according to a set of
;; style options, see <http://clang.llvm.org/docs/ClangFormatStyleOptions.html>.
;; Note that clang-format 3.4 or newer is required.
;; clang-format.el is available via MELPA and can be installed via
;;
;; M-x package-install clang-format
;;
;; when ("melpa" . "http://melpa.org/packages/") is included in
;; `package-archives'. Alternatively, ensure the directory of this
;; file is in your `load-path' and add
;;
;; (require 'clang-format)
;;
;; to your .emacs configuration.
;; You may also want to bind `clang-format-region' to a key:
;;
;; (global-set-key [C-M-tab] 'clang-format-region)
;;; Code:
(require 'cl-lib)
(require 'xml)
(defgroup clang-format nil
"Format code using clang-format."
:group 'tools)
(defcustom clang-format-executable
(or (executable-find "clang-format")
"clang-format")
"Location of the clang-format executable.
A string containing the name or the full path of the executable."
:group 'clang-format
:type '(file :must-match t)
:risky t)
(defcustom clang-format-style nil
"Style argument to pass to clang-format.
By default clang-format will load the style configuration from
a file named .clang-format located in one of the parent directories
of the buffer."
:group 'clang-format
:type '(choice (string) (const nil))
:safe #'stringp)
(make-variable-buffer-local 'clang-format-style)
(defcustom clang-format-fallback-style "none"
"Fallback style to pass to clang-format.
This style will be used if clang-format-style is set to \"file\"
and no .clang-format is found in the directory of the buffer or
one of parent directories. Set to \"none\" to disable formatting
in such buffers."
:group 'clang-format
:type 'string
:safe #'stringp)
(make-variable-buffer-local 'clang-format-fallback-style)
(defun clang-format--extract (xml-node)
"Extract replacements and cursor information from XML-NODE."
(unless (and (listp xml-node) (eq (xml-node-name xml-node) 'replacements))
(error "Expected <replacements> node"))
(let ((nodes (xml-node-children xml-node))
(incomplete-format (xml-get-attribute xml-node 'incomplete_format))
replacements
cursor)
(dolist (node nodes)
(when (listp node)
(let* ((children (xml-node-children node))
(text (car children)))
(cl-case (xml-node-name node)
(replacement
(let* ((offset (xml-get-attribute-or-nil node 'offset))
(length (xml-get-attribute-or-nil node 'length)))
(when (or (null offset) (null length))
(error "<replacement> node does not have offset and length attributes"))
(when (cdr children)
(error "More than one child node in <replacement> node"))
(setq offset (string-to-number offset))
(setq length (string-to-number length))
(push (list offset length text) replacements)))
(cursor
(setq cursor (string-to-number text)))))))
;; Sort by decreasing offset, length.
(setq replacements (sort (delq nil replacements)
(lambda (a b)
(or (> (car a) (car b))
(and (= (car a) (car b))
(> (cadr a) (cadr b)))))))
(list replacements cursor (string= incomplete-format "true"))))
(defun clang-format--replace (offset length &optional text)
"Replace the region defined by OFFSET and LENGTH with TEXT.
OFFSET and LENGTH are measured in bytes, not characters. OFFSET
is a zero-based file offset, assuming utf-8-unix coding."
(let ((start (clang-format--filepos-to-bufferpos offset 'exact 'utf-8-unix))
(end (clang-format--filepos-to-bufferpos (+ offset length) 'exact
'utf-8-unix)))
(goto-char start)
(delete-region start end)
(when text
(insert text))))
;; bufferpos-to-filepos and filepos-to-bufferpos are new in Emacs 25.1.
;; Provide fallbacks for older versions.
(defalias 'clang-format--bufferpos-to-filepos
(if (fboundp 'bufferpos-to-filepos)
'bufferpos-to-filepos
(lambda (position &optional _quality _coding-system)
(1- (position-bytes position)))))
(defalias 'clang-format--filepos-to-bufferpos
(if (fboundp 'filepos-to-bufferpos)
'filepos-to-bufferpos
(lambda (byte &optional _quality _coding-system)
(byte-to-position (1+ byte)))))
;;;###autoload
(defun clang-format-region (start end &optional style assume-file-name)
"Use clang-format to format the code between START and END according to STYLE.
If called interactively uses the region or the current statement if there is no
no active region. If no STYLE is given uses `clang-format-style'. Use
ASSUME-FILE-NAME to locate a style config file, if no ASSUME-FILE-NAME is given
uses the function `buffer-file-name'."
(interactive
(if (use-region-p)
(list (region-beginning) (region-end))
(list (point) (point))))
(unless style
(setq style clang-format-style))
(unless assume-file-name
(setq assume-file-name (buffer-file-name (buffer-base-buffer))))
(let ((file-start (clang-format--bufferpos-to-filepos start 'approximate
'utf-8-unix))
(file-end (clang-format--bufferpos-to-filepos end 'approximate
'utf-8-unix))
(cursor (clang-format--bufferpos-to-filepos (point) 'exact 'utf-8-unix))
(temp-buffer (generate-new-buffer " *clang-format-temp*"))
(temp-file (make-temp-file "clang-format"))
;; Output is XML, which is always UTF-8. Input encoding should match
;; the encoding used to convert between buffer and file positions,
;; otherwise the offsets calculated above are off. For simplicity, we
;; always use utf-8-unix and ignore the buffer coding system.
(default-process-coding-system '(utf-8-unix . utf-8-unix)))
(unwind-protect
(let ((status (apply #'call-process-region
nil nil clang-format-executable
nil `(,temp-buffer ,temp-file) nil
`("-output-replacements-xml"
;; Guard against a nil assume-file-name.
;; If the clang-format option -assume-filename
;; is given a blank string it will crash as per
;; the following bug report
;; https://bugs.llvm.org/show_bug.cgi?id=34667
,@(and assume-file-name
(list "-assume-filename" assume-file-name))
,@(and style (list "-style" style))
"-fallback-style" ,clang-format-fallback-style
"-offset" ,(number-to-string file-start)
"-length" ,(number-to-string (- file-end file-start))
"-cursor" ,(number-to-string cursor))))
(stderr (with-temp-buffer
(unless (zerop (cadr (insert-file-contents temp-file)))
(insert ": "))
(buffer-substring-no-properties
(point-min) (line-end-position)))))
(cond
((stringp status)
(error "(clang-format killed by signal %s%s)" status stderr))
((not (zerop status))
(error "(clang-format failed with code %d%s)" status stderr)))
(cl-destructuring-bind (replacements cursor incomplete-format)
(with-current-buffer temp-buffer
(clang-format--extract (car (xml-parse-region))))
(save-excursion
(dolist (rpl replacements)
(apply #'clang-format--replace rpl)))
(when cursor
(goto-char (clang-format--filepos-to-bufferpos cursor 'exact
'utf-8-unix)))
(if incomplete-format
(message "(clang-format: incomplete (syntax errors)%s)" stderr)
(message "(clang-format: success%s)" stderr))))
(delete-file temp-file)
(when (buffer-name temp-buffer) (kill-buffer temp-buffer)))))
;;;###autoload
(defun clang-format-buffer (&optional style assume-file-name)
"Use clang-format to format the current buffer according to STYLE.
If no STYLE is given uses `clang-format-style'. Use ASSUME-FILE-NAME
to locate a style config file. If no ASSUME-FILE-NAME is given uses
the function `buffer-file-name'."
(interactive)
(clang-format-region (point-min) (point-max) style assume-file-name))
;;;###autoload
(defalias 'clang-format 'clang-format-region)
(provide 'clang-format)
;;; clang-format.el ends here

View File

@ -0,0 +1,168 @@
# This file is a minimal clang-format vim-integration. To install:
# - Change 'binary' if clang-format is not on the path (see below).
# - Add to your .vimrc:
#
# if has('python')
# map <C-I> :pyf <path-to-this-file>/clang-format.py<cr>
# imap <C-I> <c-o>:pyf <path-to-this-file>/clang-format.py<cr>
# elseif has('python3')
# map <C-I> :py3f <path-to-this-file>/clang-format.py<cr>
# imap <C-I> <c-o>:py3f <path-to-this-file>/clang-format.py<cr>
# endif
#
# The if-elseif-endif conditional should pick either the python3 or python2
# integration depending on your vim setup.
#
# The first mapping enables clang-format for NORMAL and VISUAL mode, the second
# mapping adds support for INSERT mode. Change "C-I" to another binding if you
# need clang-format on a different key (C-I stands for Ctrl+i).
#
# With this integration you can press the bound key and clang-format will
# format the current line in NORMAL and INSERT mode or the selected region in
# VISUAL mode. The line or region is extended to the next bigger syntactic
# entity.
#
# You can also pass in the variable "l:lines" to choose the range for
# formatting. This variable can either contain "<start line>:<end line>" or
# "all" to format the full file. So, to format the full file, write a function
# like:
# :function FormatFile()
# : let l:lines="all"
# : if has('python')
# : pyf <path-to-this-file>/clang-format.py
# : elseif has('python3')
# : py3f <path-to-this-file>/clang-format.py
# : endif
# :endfunction
#
# It operates on the current, potentially unsaved buffer and does not create
# or save any files. To revert a formatting, just undo.
from __future__ import absolute_import, division, print_function
import difflib
import json
import os.path
import platform
import subprocess
import sys
import vim
# set g:clang_format_path to the path to clang-format if it is not on the path
# Change this to the full path if clang-format is not on the path.
binary = "clang-format"
if vim.eval('exists("g:clang_format_path")') == "1":
binary = vim.eval("g:clang_format_path")
# Change this to format according to other formatting styles. See the output of
# 'clang-format --help' for a list of supported styles. The default looks for
# a '.clang-format' or '_clang-format' file to indicate the style that should be
# used.
style = None
fallback_style = None
if vim.eval('exists("g:clang_format_fallback_style")') == "1":
fallback_style = vim.eval("g:clang_format_fallback_style")
def get_buffer(encoding):
if platform.python_version_tuple()[0] == "3":
return vim.current.buffer
return [line.decode(encoding) for line in vim.current.buffer]
def main():
# Get the current text.
encoding = vim.eval("&encoding")
buf = get_buffer(encoding)
# Join the buffer into a single string with a terminating newline
text = ("\n".join(buf) + "\n").encode(encoding)
# Determine range to format.
if vim.eval('exists("l:lines")') == "1":
lines = ["-lines", vim.eval("l:lines")]
elif vim.eval('exists("l:formatdiff")') == "1" and os.path.exists(
vim.current.buffer.name
):
with open(vim.current.buffer.name, "r") as f:
ondisk = f.read().splitlines()
sequence = difflib.SequenceMatcher(None, ondisk, vim.current.buffer)
lines = []
for op in reversed(sequence.get_opcodes()):
if op[0] not in ["equal", "delete"]:
lines += ["-lines", "%s:%s" % (op[3] + 1, op[4])]
if lines == []:
return
else:
lines = [
"-lines",
"%s:%s" % (vim.current.range.start + 1, vim.current.range.end + 1),
]
# Convert cursor (line, col) to bytes.
# Don't use line2byte: https://github.com/vim/vim/issues/5930
_, cursor_line, cursor_col, _ = vim.eval('getpos(".")') # 1-based
cursor_byte = 0
for line in text.split(b"\n")[: int(cursor_line) - 1]:
cursor_byte += len(line) + 1
cursor_byte += int(cursor_col) - 1
if cursor_byte < 0:
print("Couldn't determine cursor position. Is your file empty?")
return
# Avoid flashing an ugly, ugly cmd prompt on Windows when invoking clang-format.
startupinfo = None
if sys.platform.startswith("win32"):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# Call formatter.
command = [binary, "-cursor", str(cursor_byte)]
if lines != ["-lines", "all"]:
command += lines
if style:
command.extend(["-style", style])
if fallback_style:
command.extend(["-fallback-style", fallback_style])
if vim.current.buffer.name:
command.extend(["-assume-filename", vim.current.buffer.name])
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=startupinfo,
)
stdout, stderr = p.communicate(input=text)
# If successful, replace buffer contents.
if stderr:
print(stderr)
if not stdout:
print(
"No output from clang-format (crashed?).\n"
"Please report to bugs.llvm.org."
)
else:
header, content = stdout.split(b"\n", 1)
header = json.loads(header.decode("utf-8"))
# Strip off the trailing newline (added above).
# This maintains trailing empty lines present in the buffer if
# the -lines specification requests them to remain unchanged.
lines = content.decode(encoding).split("\n")[:-1]
sequence = difflib.SequenceMatcher(None, buf, lines)
for op in reversed(sequence.get_opcodes()):
if op[0] != "equal":
vim.current.buffer[op[1] : op[2]] = lines[op[3] : op[4]]
if header.get("IncompleteFormat"):
print("clang-format: incomplete (syntax errors)")
# Convert cursor bytes to (line, col)
# Don't use goto: https://github.com/vim/vim/issues/5930
cursor_byte = int(header["Cursor"])
prefix = content[0:cursor_byte]
cursor_line = 1 + prefix.count(b"\n")
cursor_column = 1 + len(prefix.rsplit(b"\n", 1)[-1])
vim.command("call cursor(%d, %d)" % (cursor_line, cursor_column))
main()

View File

@ -0,0 +1,460 @@
;;; clang-include-fixer.el --- Emacs integration of the clang include fixer -*- lexical-binding: t; -*-
;; Keywords: tools, c
;; Package-Requires: ((cl-lib "0.5") (json "1.2") (let-alist "1.0.4"))
;;; Commentary:
;; This package allows Emacs users to invoke the 'clang-include-fixer' within
;; Emacs. 'clang-include-fixer' provides an automated way of adding #include
;; directives for missing symbols in one translation unit, see
;; <http://clang.llvm.org/extra/clang-include-fixer.html>.
;;; Code:
(require 'cl-lib)
(require 'json)
(require 'let-alist)
(defgroup clang-include-fixer nil
"Clang-based include fixer."
:group 'tools)
(defvar clang-include-fixer-add-include-hook nil
"A hook that will be called for every added include.
The first argument is the filename of the include, the second argument is
non-nil if the include is a system-header.")
(defcustom clang-include-fixer-executable
"clang-include-fixer"
"Location of the clang-include-fixer executable.
A string containing the name or the full path of the executable."
:group 'clang-include-fixer
:type '(file :must-match t)
:risky t)
(defcustom clang-include-fixer-input-format
'yaml
"Input format for clang-include-fixer.
This string is passed as -db argument to
`clang-include-fixer-executable'."
:group 'clang-include-fixer
:type '(radio
(const :tag "Hard-coded mapping" :fixed)
(const :tag "YAML" yaml)
(symbol :tag "Other"))
:risky t)
(defcustom clang-include-fixer-init-string
""
"Database initialization string for clang-include-fixer.
This string is passed as -input argument to
`clang-include-fixer-executable'."
:group 'clang-include-fixer
:type 'string
:risky t)
(defface clang-include-fixer-highlight '((t :background "green"))
"Used for highlighting the symbol for which a header file is being added.")
;;;###autoload
(defun clang-include-fixer ()
"Invoke the Include Fixer to insert missing C++ headers."
(interactive)
(message (concat "Calling the include fixer. "
"This might take some seconds. Please wait."))
(clang-include-fixer--start #'clang-include-fixer--add-header
"-output-headers"))
;;;###autoload
(defun clang-include-fixer-at-point ()
"Invoke the Clang include fixer for the symbol at point."
(interactive)
(let ((symbol (clang-include-fixer--symbol-at-point)))
(unless symbol
(user-error "No symbol at current location"))
(clang-include-fixer-from-symbol symbol)))
;;;###autoload
(defun clang-include-fixer-from-symbol (symbol)
"Invoke the Clang include fixer for the SYMBOL.
When called interactively, prompts the user for a symbol."
(interactive
(list (read-string "Symbol: " (clang-include-fixer--symbol-at-point))))
(clang-include-fixer--start #'clang-include-fixer--add-header
(format "-query-symbol=%s" symbol)))
(defun clang-include-fixer--start (callback &rest args)
"Asynchronously start clang-include-fixer with parameters ARGS.
The current file name is passed after ARGS as last argument. If
the call was successful the returned result is stored in a
temporary buffer, and CALLBACK is called with the temporary
buffer as only argument."
(unless buffer-file-name
(user-error "clang-include-fixer works only in buffers that visit a file"))
(let ((process (if (and (fboundp 'make-process)
;; make-process doesnt support remote files
;; (https://debbugs.gnu.org/cgi/bugreport.cgi?bug=28691).
(not (find-file-name-handler default-directory
'start-file-process)))
;; Prefer using make-process if possible, because
;; start-process doesnt allow us to separate the
;; standard error from the output.
(clang-include-fixer--make-process callback args)
(clang-include-fixer--start-process callback args))))
(save-restriction
(widen)
(process-send-region process (point-min) (point-max)))
(process-send-eof process))
nil)
(defun clang-include-fixer--make-process (callback args)
"Start a new clang-include-fixer process using `make-process'.
CALLBACK is called after the process finishes successfully; it is
called with a single argument, the buffer where standard output
has been inserted. ARGS is a list of additional command line
arguments. Return the new process object."
(let ((stdin (current-buffer))
(stdout (generate-new-buffer "*clang-include-fixer output*"))
(stderr (generate-new-buffer "*clang-include-fixer errors*")))
(make-process :name "clang-include-fixer"
:buffer stdout
:command (clang-include-fixer--command args)
:coding 'utf-8-unix
:noquery t
:connection-type 'pipe
:sentinel (clang-include-fixer--sentinel stdin stdout stderr
callback)
:stderr stderr)))
(defun clang-include-fixer--start-process (callback args)
"Start a new clang-include-fixer process using `start-file-process'.
CALLBACK is called after the process finishes successfully; it is
called with a single argument, the buffer where standard output
has been inserted. ARGS is a list of additional command line
arguments. Return the new process object."
(let* ((stdin (current-buffer))
(stdout (generate-new-buffer "*clang-include-fixer output*"))
(process-connection-type nil)
(process (apply #'start-file-process "clang-include-fixer" stdout
(clang-include-fixer--command args))))
(set-process-coding-system process 'utf-8-unix 'utf-8-unix)
(set-process-query-on-exit-flag process nil)
(set-process-sentinel process
(clang-include-fixer--sentinel stdin stdout nil
callback))
process))
(defun clang-include-fixer--command (args)
"Return the clang-include-fixer command line.
Returns a list; the first element is the binary to
execute (`clang-include-fixer-executable'), and the remaining
elements are the command line arguments. Adds proper arguments
for `clang-include-fixer-input-format' and
`clang-include-fixer-init-string'. Appends the current buffer's
file name; prepends ARGS directly in front of it."
(cl-check-type args list)
`(,clang-include-fixer-executable
,(format "-db=%s" clang-include-fixer-input-format)
,(format "-input=%s" clang-include-fixer-init-string)
"-stdin"
,@args
,(clang-include-fixer--file-local-name buffer-file-name)))
(defun clang-include-fixer--sentinel (stdin stdout stderr callback)
"Return a process sentinel for clang-include-fixer processes.
STDIN, STDOUT, and STDERR are buffers for the standard streams;
only STDERR may be nil. CALLBACK is called in the case of
success; it is called with a single argument, STDOUT. On
failure, a buffer containing the error output is displayed."
(cl-check-type stdin buffer)
(cl-check-type stdout buffer)
(cl-check-type stderr (or null buffer))
(cl-check-type callback function)
(lambda (process event)
(cl-check-type process process)
(cl-check-type event string)
(unwind-protect
(if (string-equal event "finished\n")
(progn
(when stderr (kill-buffer stderr))
(with-current-buffer stdin
(funcall callback stdout))
(kill-buffer stdout))
(when stderr (kill-buffer stdout))
(message "clang-include-fixer failed")
(with-current-buffer (or stderr stdout)
(insert "\nProcess " (process-name process)
?\s event))
(display-buffer (or stderr stdout))))
nil))
(defun clang-include-fixer--replace-buffer (stdout)
"Replace current buffer by content of STDOUT."
(cl-check-type stdout buffer)
(barf-if-buffer-read-only)
(cond ((fboundp 'replace-buffer-contents) (replace-buffer-contents stdout))
((clang-include-fixer--insert-line stdout (current-buffer)))
(t (erase-buffer) (insert-buffer-substring stdout)))
(message "Fix applied")
nil)
(defun clang-include-fixer--insert-line (from to)
"Insert a single missing line from the buffer FROM into TO.
FROM and TO must be buffers. If the contents of FROM and TO are
equal, do nothing and return non-nil. If FROM contains a single
line missing from TO, insert that line into TO so that the buffer
contents are equal and return non-nil. Otherwise, do nothing and
return nil. Buffer restrictions are ignored."
(cl-check-type from buffer)
(cl-check-type to buffer)
(with-current-buffer from
(save-excursion
(save-restriction
(widen)
(with-current-buffer to
(save-excursion
(save-restriction
(widen)
;; Search for the first buffer difference.
(let ((chars (abs (compare-buffer-substrings to nil nil from nil nil))))
(if (zerop chars)
;; Buffer contents are equal, nothing to do.
t
(goto-char chars)
;; We might have ended up in the middle of a line if the
;; current line partially matches. In this case we would
;; have to insert more than a line. Move to the beginning of
;; the line to avoid this situation.
(beginning-of-line)
(with-current-buffer from
(goto-char chars)
(beginning-of-line)
(let ((from-begin (point))
(from-end (progn (forward-line) (point)))
(to-point (with-current-buffer to (point))))
;; Search for another buffer difference after the line in
;; question. If there is none, we can proceed.
(when (zerop (compare-buffer-substrings from from-end nil
to to-point nil))
(with-current-buffer to
(insert-buffer-substring from from-begin from-end))
t))))))))))))
(defun clang-include-fixer--add-header (stdout)
"Analyse the result of clang-include-fixer stored in STDOUT.
Add a missing header if there is any. If there are multiple
possible headers the user can select one of them to be included.
Temporarily highlight the affected symbols. Asynchronously call
clang-include-fixer to insert the selected header."
(cl-check-type stdout buffer-live)
(let ((context (clang-include-fixer--parse-json stdout)))
(let-alist context
(cond
((null .QuerySymbolInfos)
(message "The file is fine, no need to add a header."))
((null .HeaderInfos)
(message "Couldn't find header for '%s'"
(let-alist (car .QuerySymbolInfos) .RawIdentifier)))
(t
;; Users may C-g in prompts, make sure the process sentinel
;; behaves correctly.
(with-local-quit
;; Replace the HeaderInfos list by a single header selected by
;; the user.
(clang-include-fixer--select-header context)
;; Call clang-include-fixer again to insert the selected header.
(clang-include-fixer--start
(let ((old-tick (buffer-chars-modified-tick)))
(lambda (stdout)
(when (/= old-tick (buffer-chars-modified-tick))
;; Replacing the buffer now would undo the users changes.
(user-error (concat "The buffer has been changed "
"before the header could be inserted")))
(clang-include-fixer--replace-buffer stdout)
(let-alist context
(let-alist (car .HeaderInfos)
(with-local-quit
(run-hook-with-args 'clang-include-fixer-add-include-hook
(substring .Header 1 -1)
(string= (substring .Header 0 1) "<")))))))
(format "-insert-header=%s"
(clang-include-fixer--encode-json context))))))))
nil)
(defun clang-include-fixer--select-header (context)
"Prompt the user for a header if necessary.
CONTEXT must be a clang-include-fixer context object in
association list format. If it contains more than one HeaderInfo
element, prompt the user to select one of the headers. CONTEXT
is modified to include only the selected element."
(cl-check-type context cons)
(let-alist context
(if (cdr .HeaderInfos)
(clang-include-fixer--prompt-for-header context)
(message "Only one include is missing: %s"
(let-alist (car .HeaderInfos) .Header))))
nil)
(defvar clang-include-fixer--history nil
"History for `clang-include-fixer--prompt-for-header'.")
(defun clang-include-fixer--prompt-for-header (context)
"Prompt the user for a single header.
The choices are taken from the HeaderInfo elements in CONTEXT.
They are replaced by the single element selected by the user."
(let-alist context
(let ((symbol (clang-include-fixer--symbol-name .QuerySymbolInfos))
;; Add temporary highlighting so that the user knows which
;; symbols the current session is about.
(overlays (remove nil
(mapcar #'clang-include-fixer--highlight .QuerySymbolInfos))))
(unwind-protect
(save-excursion
;; While prompting, go to the closest overlay so that the user sees
;; some context.
(when overlays
(goto-char (clang-include-fixer--closest-overlay overlays)))
(cl-flet ((header (info) (let-alist info .Header)))
;; The header-infos is already sorted by clang-include-fixer.
(let* ((headers (mapcar #'header .HeaderInfos))
(header (completing-read
(clang-include-fixer--format-message
"Select include for '%s': " symbol)
headers nil :require-match nil
'clang-include-fixer--history
;; Specify a default to prevent the behavior
;; described in
;; https://github.com/DarwinAwardWinner/ido-completing-read-plus#why-does-ret-sometimes-not-select-the-first-completion-on-the-list--why-is-there-an-empty-entry-at-the-beginning-of-the-completion-list--what-happened-to-old-style-default-selection.
(car headers)))
(info (cl-find header .HeaderInfos :key #'header :test #'string=)))
(unless info (user-error "No header selected"))
(setcar .HeaderInfos info)
(setcdr .HeaderInfos nil))))
(mapc #'delete-overlay overlays)))))
(defun clang-include-fixer--symbol-name (symbol-infos)
"Return the unique symbol name in SYMBOL-INFOS.
Raise a signal if the symbol name is not unique."
(let ((symbols (delete-dups (mapcar (lambda (info)
(let-alist info .RawIdentifier))
symbol-infos))))
(when (cdr symbols)
(error "Multiple symbols %s returned" symbols))
(car symbols)))
(defun clang-include-fixer--highlight (symbol-info)
"Add an overlay to highlight SYMBOL-INFO, if it points to a non-empty range.
Return the overlay object, or nil."
(let-alist symbol-info
(unless (zerop .Range.Length)
(let ((overlay (make-overlay
(clang-include-fixer--filepos-to-bufferpos
.Range.Offset 'approximate)
(clang-include-fixer--filepos-to-bufferpos
(+ .Range.Offset .Range.Length) 'approximate))))
(overlay-put overlay 'face 'clang-include-fixer-highlight)
overlay))))
(defun clang-include-fixer--closest-overlay (overlays)
"Return the start of the overlay in OVERLAYS that is closest to point."
(cl-check-type overlays cons)
(let ((point (point))
acc)
(dolist (overlay overlays acc)
(let ((start (overlay-start overlay)))
(when (or (null acc) (< (abs (- point start)) (abs (- point acc))))
(setq acc start))))))
(defun clang-include-fixer--parse-json (buffer)
"Parse a JSON response from clang-include-fixer in BUFFER.
Return the JSON object as an association list."
(with-current-buffer buffer
(save-excursion
(goto-char (point-min))
(let ((json-object-type 'alist)
(json-array-type 'list)
(json-key-type 'symbol)
(json-false :json-false)
(json-null nil)
(json-pre-element-read-function nil)
(json-post-element-read-function nil))
(json-read)))))
(defun clang-include-fixer--encode-json (object)
"Return the JSON representation of OBJECT as a string."
(let ((json-encoding-separator ",")
(json-encoding-default-indentation " ")
(json-encoding-pretty-print nil)
(json-encoding-lisp-style-closings nil)
(json-encoding-object-sort-predicate nil))
(json-encode object)))
(defun clang-include-fixer--symbol-at-point ()
"Return the qualified symbol at point.
If there is no symbol at point, return nil."
;; Let bounds-of-thing-at-point to do the hard work and deal with edge
;; cases.
(let ((bounds (bounds-of-thing-at-point 'symbol)))
(when bounds
(let ((beg (car bounds))
(end (cdr bounds)))
(save-excursion
;; Extend the symbol range to the left. Skip over namespace
;; delimiters and parent namespace names.
(goto-char beg)
(while (and (clang-include-fixer--skip-double-colon-backward)
(skip-syntax-backward "w_")))
;; Skip over one more namespace delimiter, for absolute names.
(clang-include-fixer--skip-double-colon-backward)
(setq beg (point))
;; Extend the symbol range to the right. Skip over namespace
;; delimiters and child namespace names.
(goto-char end)
(while (and (clang-include-fixer--skip-double-colon-forward)
(skip-syntax-forward "w_")))
(setq end (point)))
(buffer-substring-no-properties beg end)))))
(defun clang-include-fixer--skip-double-colon-forward ()
"Skip a double colon.
When the next two characters are '::', skip them and return
non-nil. Otherwise return nil."
(let ((end (+ (point) 2)))
(when (and (<= end (point-max))
(string-equal (buffer-substring-no-properties (point) end) "::"))
(goto-char end)
t)))
(defun clang-include-fixer--skip-double-colon-backward ()
"Skip a double colon.
When the previous two characters are '::', skip them and return
non-nil. Otherwise return nil."
(let ((beg (- (point) 2)))
(when (and (>= beg (point-min))
(string-equal (buffer-substring-no-properties beg (point)) "::"))
(goto-char beg)
t)))
;; filepos-to-bufferpos is new in Emacs 25.1. Provide a fallback for older
;; versions.
(defalias 'clang-include-fixer--filepos-to-bufferpos
(if (fboundp 'filepos-to-bufferpos)
'filepos-to-bufferpos
(lambda (byte &optional _quality _coding-system)
(byte-to-position (1+ byte)))))
;; format-message is new in Emacs 25.1. Provide a fallback for older
;; versions.
(defalias 'clang-include-fixer--format-message
(if (fboundp 'format-message) 'format-message 'format))
;; file-local-name is new in Emacs 26.1. Provide a fallback for older
;; versions.
(defalias 'clang-include-fixer--file-local-name
(if (fboundp 'file-local-name) #'file-local-name
(lambda (file) (or (file-remote-p file 'localname) file))))
(provide 'clang-include-fixer)
;;; clang-include-fixer.el ends here

View File

@ -0,0 +1,243 @@
# This file is a minimal clang-include-fixer vim-integration. To install:
# - Change 'binary' if clang-include-fixer is not on the path (see below).
# - Add to your .vimrc:
#
# noremap <leader>cf :pyf path/to/llvm/source/tools/clang/tools/extra/clang-include-fixer/tool/clang-include-fixer.py<cr>
#
# This enables clang-include-fixer for NORMAL and VISUAL mode. Change
# "<leader>cf" to another binding if you need clang-include-fixer on a
# different key.
#
# To set up clang-include-fixer, see
# http://clang.llvm.org/extra/clang-include-fixer.html
#
# With this integration you can press the bound key and clang-include-fixer will
# be run on the current buffer.
#
# It operates on the current, potentially unsaved buffer and does not create
# or save any files. To revert a fix, just undo.
from __future__ import print_function
import argparse
import difflib
import json
import re
import subprocess
import vim
# set g:clang_include_fixer_path to the path to clang-include-fixer if it is not
# on the path.
# Change this to the full path if clang-include-fixer is not on the path.
binary = "clang-include-fixer"
if vim.eval('exists("g:clang_include_fixer_path")') == "1":
binary = vim.eval("g:clang_include_fixer_path")
maximum_suggested_headers = 3
if vim.eval('exists("g:clang_include_fixer_maximum_suggested_headers")') == "1":
maximum_suggested_headers = max(
1, vim.eval("g:clang_include_fixer_maximum_suggested_headers")
)
increment_num = 5
if vim.eval('exists("g:clang_include_fixer_increment_num")') == "1":
increment_num = max(1, vim.eval("g:clang_include_fixer_increment_num"))
jump_to_include = False
if vim.eval('exists("g:clang_include_fixer_jump_to_include")') == "1":
jump_to_include = vim.eval("g:clang_include_fixer_jump_to_include") != "0"
query_mode = False
if vim.eval('exists("g:clang_include_fixer_query_mode")') == "1":
query_mode = vim.eval("g:clang_include_fixer_query_mode") != "0"
def GetUserSelection(message, headers, maximum_suggested_headers):
eval_message = message + "\n"
for idx, header in enumerate(headers[0:maximum_suggested_headers]):
eval_message += "({0}). {1}\n".format(idx + 1, header)
eval_message += "Enter (q) to quit;"
if maximum_suggested_headers < len(headers):
eval_message += " (m) to show {0} more candidates.".format(
min(increment_num, len(headers) - maximum_suggested_headers)
)
eval_message += "\nSelect (default 1): "
res = vim.eval("input('{0}')".format(eval_message))
if res == "":
# choose the top ranked header by default
idx = 1
elif res == "q":
raise Exception(" Insertion cancelled...")
elif res == "m":
return GetUserSelection(
message, headers, maximum_suggested_headers + increment_num
)
else:
try:
idx = int(res)
if idx <= 0 or idx > len(headers):
raise Exception()
except Exception:
# Show a new prompt on invalid option instead of aborting so that users
# don't need to wait for another clang-include-fixer run.
print("Invalid option: {}".format(res), file=sys.stderr)
return GetUserSelection(message, headers, maximum_suggested_headers)
return headers[idx - 1]
def execute(command, text):
# Avoid flashing a cmd prompt on Windows.
startupinfo = None
if sys.platform.startswith("win32"):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
p = subprocess.Popen(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE,
startupinfo=startupinfo,
)
return p.communicate(input=text.encode("utf-8"))
def InsertHeaderToVimBuffer(header, text):
command = [
binary,
"-stdin",
"-insert-header=" + json.dumps(header),
vim.current.buffer.name,
]
stdout, stderr = execute(command, text)
if stderr:
raise Exception(stderr)
if stdout:
lines = stdout.splitlines()
sequence = difflib.SequenceMatcher(None, vim.current.buffer, lines)
line_num = None
for op in reversed(sequence.get_opcodes()):
if op[0] != "equal":
vim.current.buffer[op[1] : op[2]] = lines[op[3] : op[4]]
if op[0] == "insert":
# line_num in vim is 1-based.
line_num = op[1] + 1
if jump_to_include and line_num:
vim.current.window.cursor = (line_num, 0)
# The vim internal implementation (expand("cword"/"cWORD")) doesn't support
# our use case very well, we re-implement our own one.
def get_symbol_under_cursor():
line = vim.eval('line(".")')
# column number in vim is 1-based.
col = int(vim.eval('col(".")')) - 1
line_text = vim.eval("getline({0})".format(line))
if len(line_text) == 0:
return ""
symbol_pos_begin = col
p = re.compile("[a-zA-Z0-9:_]")
while symbol_pos_begin >= 0 and p.match(line_text[symbol_pos_begin]):
symbol_pos_begin -= 1
symbol_pos_end = col
while symbol_pos_end < len(line_text) and p.match(line_text[symbol_pos_end]):
symbol_pos_end += 1
return line_text[symbol_pos_begin + 1 : symbol_pos_end]
def main():
parser = argparse.ArgumentParser(
description="Vim integration for clang-include-fixer"
)
parser.add_argument("-db", default="yaml", help="clang-include-fixer input format.")
parser.add_argument("-input", default="", help="String to initialize the database.")
# Don't throw exception when parsing unknown arguments to make the script
# work in neovim.
# Neovim (at least v0.2.1) somehow mangles the sys.argv in a weird way: it
# will pass additional arguments (e.g. "-c script_host.py") to sys.argv,
# which makes the script fail.
args, _ = parser.parse_known_args()
# Get the current text.
buf = vim.current.buffer
text = "\n".join(buf)
if query_mode:
symbol = get_symbol_under_cursor()
if len(symbol) == 0:
print("Skip querying empty symbol.")
return
command = [
binary,
"-stdin",
"-query-symbol=" + get_symbol_under_cursor(),
"-db=" + args.db,
"-input=" + args.input,
vim.current.buffer.name,
]
else:
# Run command to get all headers.
command = [
binary,
"-stdin",
"-output-headers",
"-db=" + args.db,
"-input=" + args.input,
vim.current.buffer.name,
]
stdout, stderr = execute(command, text)
if stderr:
print(
"Error while running clang-include-fixer: {}".format(stderr),
file=sys.stderr,
)
return
include_fixer_context = json.loads(stdout)
query_symbol_infos = include_fixer_context["QuerySymbolInfos"]
if not query_symbol_infos:
print("The file is fine, no need to add a header.")
return
symbol = query_symbol_infos[0]["RawIdentifier"]
# The header_infos is already sorted by clang-include-fixer.
header_infos = include_fixer_context["HeaderInfos"]
# Deduplicate headers while keeping the order, so that the same header would
# not be suggested twice.
unique_headers = []
seen = set()
for header_info in header_infos:
header = header_info["Header"]
if header not in seen:
seen.add(header)
unique_headers.append(header)
if not unique_headers:
print("Couldn't find a header for {0}.".format(symbol))
return
try:
selected = unique_headers[0]
inserted_header_infos = header_infos
if len(unique_headers) > 1:
selected = GetUserSelection(
"choose a header file for {0}.".format(symbol),
unique_headers,
maximum_suggested_headers,
)
inserted_header_infos = [
header for header in header_infos if header["Header"] == selected
]
include_fixer_context["HeaderInfos"] = inserted_header_infos
InsertHeaderToVimBuffer(include_fixer_context, text)
print("Added #include {0} for {1}.".format(selected, symbol))
except Exception as error:
print(error, file=sys.stderr)
return
if __name__ == "__main__":
main()

View File

@ -0,0 +1,79 @@
;;; clang-rename.el --- Renames every occurrence of a symbol found at <offset>. -*- lexical-binding: t; -*-
;; Keywords: tools, c
;;; Commentary:
;; To install clang-rename.el make sure the directory of this file is in your
;; `load-path' and add
;;
;; (require 'clang-rename)
;;
;; to your .emacs configuration.
;;; Code:
(defgroup clang-rename nil
"Integration with clang-rename"
:group 'c)
(defcustom clang-rename-binary "clang-rename"
"Path to clang-rename executable."
:type '(file :must-match t)
:group 'clang-rename)
;;;###autoload
(defun clang-rename (new-name)
"Rename all instances of the symbol at point to NEW-NAME using clang-rename."
(interactive "sEnter a new name: ")
(save-some-buffers :all)
;; clang-rename should not be combined with other operations when undoing.
(undo-boundary)
(let ((output-buffer (get-buffer-create "*clang-rename*")))
(with-current-buffer output-buffer (erase-buffer))
(let ((exit-code (call-process
clang-rename-binary nil output-buffer nil
(format "-offset=%d"
;; clang-rename wants file (byte) offsets, not
;; buffer (character) positions.
(clang-rename--bufferpos-to-filepos
;; Emacs treats one character after a symbol as
;; part of the symbol, but clang-rename doesnt.
;; Use the beginning of the current symbol, if
;; available, to resolve the inconsistency.
(or (car (bounds-of-thing-at-point 'symbol))
(point))
'exact))
(format "-new-name=%s" new-name)
"-i" (buffer-file-name))))
(if (and (integerp exit-code) (zerop exit-code))
;; Success; revert current buffer so it gets the modifications.
(progn
(kill-buffer output-buffer)
(revert-buffer :ignore-auto :noconfirm :preserve-modes))
;; Failure; append exit code to output buffer and display it.
(let ((message (clang-rename--format-message
"clang-rename failed with %s %s"
(if (integerp exit-code) "exit status" "signal")
exit-code)))
(with-current-buffer output-buffer
(insert ?\n message ?\n))
(message "%s" message)
(display-buffer output-buffer))))))
(defalias 'clang-rename--bufferpos-to-filepos
(if (fboundp 'bufferpos-to-filepos)
'bufferpos-to-filepos
;; Emacs 24 doesnt have bufferpos-to-filepos, simulate it using
;; position-bytes.
(lambda (position &optional _quality _coding-system)
(1- (position-bytes position)))))
;; format-message is new in Emacs 25.1. Provide a fallback for older
;; versions.
(defalias 'clang-rename--format-message
(if (fboundp 'format-message) 'format-message 'format))
(provide 'clang-rename)
;;; clang-rename.el ends here

View File

@ -0,0 +1,70 @@
"""
Minimal clang-rename integration with Vim.
Before installing make sure one of the following is satisfied:
* clang-rename is in your PATH
* `g:clang_rename_path` in ~/.vimrc points to valid clang-rename executable
* `binary` in clang-rename.py points to valid to clang-rename executable
To install, simply put this into your ~/.vimrc for python2 support
noremap <leader>cr :pyf <path-to>/clang-rename.py<cr>
For python3 use the following command (note the change from :pyf to :py3f)
noremap <leader>cr :py3f <path-to>/clang-rename.py<cr>
IMPORTANT NOTE: Before running the tool, make sure you saved the file.
All you have to do now is to place a cursor on a variable/function/class which
you would like to rename and press '<leader>cr'. You will be prompted for a new
name if the cursor points to a valid symbol.
"""
from __future__ import absolute_import, division, print_function
import vim
import subprocess
import sys
def main():
binary = "clang-rename"
if vim.eval('exists("g:clang_rename_path")') == "1":
binary = vim.eval("g:clang_rename_path")
# Get arguments for clang-rename binary.
offset = int(vim.eval('line2byte(line("."))+col(".")')) - 2
if offset < 0:
print(
"Couldn't determine cursor position. Is your file empty?", file=sys.stderr
)
return
filename = vim.current.buffer.name
new_name_request_message = "type new name:"
new_name = vim.eval("input('{}\n')".format(new_name_request_message))
# Call clang-rename.
command = [
binary,
filename,
"-i",
"-offset",
str(offset),
"-new-name",
str(new_name),
]
# FIXME: make it possible to run the tool on unsaved file.
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
if stderr:
print(stderr)
# Reload all buffers in Vim.
vim.command("checktime")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,382 @@
#!/usr/bin/env python3
#
# ===- clang-tidy-diff.py - ClangTidy Diff Checker -----------*- python -*--===#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===-----------------------------------------------------------------------===#
r"""
ClangTidy Diff Checker
======================
This script reads input from a unified diff, runs clang-tidy on all changed
files and outputs clang-tidy warnings in changed lines only. This is useful to
detect clang-tidy regressions in the lines touched by a specific patch.
Example usage for git/svn users:
git diff -U0 HEAD^ | clang-tidy-diff.py -p1
svn diff --diff-cmd=diff -x-U0 | \
clang-tidy-diff.py -fix -checks=-*,modernize-use-override
"""
import argparse
import glob
import json
import multiprocessing
import os
import re
import shutil
import subprocess
import sys
import tempfile
import threading
import traceback
try:
import yaml
except ImportError:
yaml = None
is_py2 = sys.version[0] == "2"
if is_py2:
import Queue as queue
else:
import queue as queue
def run_tidy(task_queue, lock, timeout, failed_files):
watchdog = None
while True:
command = task_queue.get()
try:
proc = subprocess.Popen(
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
if timeout is not None:
watchdog = threading.Timer(timeout, proc.kill)
watchdog.start()
stdout, stderr = proc.communicate()
if proc.returncode != 0:
if proc.returncode < 0:
msg = "Terminated by signal %d : %s\n" % (
-proc.returncode,
" ".join(command),
)
stderr += msg.encode("utf-8")
failed_files.append(command)
with lock:
sys.stdout.write(stdout.decode("utf-8") + "\n")
sys.stdout.flush()
if stderr:
sys.stderr.write(stderr.decode("utf-8") + "\n")
sys.stderr.flush()
except Exception as e:
with lock:
sys.stderr.write("Failed: " + str(e) + ": ".join(command) + "\n")
finally:
with lock:
if not (timeout is None or watchdog is None):
if not watchdog.is_alive():
sys.stderr.write(
"Terminated by timeout: " + " ".join(command) + "\n"
)
watchdog.cancel()
task_queue.task_done()
def start_workers(max_tasks, tidy_caller, arguments):
for _ in range(max_tasks):
t = threading.Thread(target=tidy_caller, args=arguments)
t.daemon = True
t.start()
def merge_replacement_files(tmpdir, mergefile):
"""Merge all replacement files in a directory into a single file"""
# The fixes suggested by clang-tidy >= 4.0.0 are given under
# the top level key 'Diagnostics' in the output yaml files
mergekey = "Diagnostics"
merged = []
for replacefile in glob.iglob(os.path.join(tmpdir, "*.yaml")):
content = yaml.safe_load(open(replacefile, "r"))
if not content:
continue # Skip empty files.
merged.extend(content.get(mergekey, []))
if merged:
# MainSourceFile: The key is required by the definition inside
# include/clang/Tooling/ReplacementsYaml.h, but the value
# is actually never used inside clang-apply-replacements,
# so we set it to '' here.
output = {"MainSourceFile": "", mergekey: merged}
with open(mergefile, "w") as out:
yaml.safe_dump(output, out)
else:
# Empty the file:
open(mergefile, "w").close()
def main():
parser = argparse.ArgumentParser(
description="Run clang-tidy against changed files, and "
"output diagnostics only for modified "
"lines."
)
parser.add_argument(
"-clang-tidy-binary",
metavar="PATH",
default="clang-tidy",
help="path to clang-tidy binary",
)
parser.add_argument(
"-p",
metavar="NUM",
default=0,
help="strip the smallest prefix containing P slashes",
)
parser.add_argument(
"-regex",
metavar="PATTERN",
default=None,
help="custom pattern selecting file paths to check "
"(case sensitive, overrides -iregex)",
)
parser.add_argument(
"-iregex",
metavar="PATTERN",
default=r".*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)",
help="custom pattern selecting file paths to check "
"(case insensitive, overridden by -regex)",
)
parser.add_argument(
"-j",
type=int,
default=1,
help="number of tidy instances to be run in parallel.",
)
parser.add_argument(
"-timeout", type=int, default=None, help="timeout per each file in seconds."
)
parser.add_argument(
"-fix", action="store_true", default=False, help="apply suggested fixes"
)
parser.add_argument(
"-checks",
help="checks filter, when not specified, use clang-tidy " "default",
default="",
)
parser.add_argument(
"-config-file",
dest="config_file",
help="Specify the path of .clang-tidy or custom config file",
default="",
)
parser.add_argument("-use-color", action="store_true", help="Use colors in output")
parser.add_argument(
"-path", dest="build_path", help="Path used to read a compile command database."
)
if yaml:
parser.add_argument(
"-export-fixes",
metavar="FILE_OR_DIRECTORY",
dest="export_fixes",
help="A directory or a yaml file to store suggested fixes in, "
"which can be applied with clang-apply-replacements. If the "
"parameter is a directory, the fixes of each compilation unit are "
"stored in individual yaml files in the directory.",
)
else:
parser.add_argument(
"-export-fixes",
metavar="DIRECTORY",
dest="export_fixes",
help="A directory to store suggested fixes in, which can be applied "
"with clang-apply-replacements. The fixes of each compilation unit are "
"stored in individual yaml files in the directory.",
)
parser.add_argument(
"-extra-arg",
dest="extra_arg",
action="append",
default=[],
help="Additional argument to append to the compiler " "command line.",
)
parser.add_argument(
"-extra-arg-before",
dest="extra_arg_before",
action="append",
default=[],
help="Additional argument to prepend to the compiler " "command line.",
)
parser.add_argument(
"-quiet",
action="store_true",
default=False,
help="Run clang-tidy in quiet mode",
)
parser.add_argument(
"-load",
dest="plugins",
action="append",
default=[],
help="Load the specified plugin in clang-tidy.",
)
clang_tidy_args = []
argv = sys.argv[1:]
if "--" in argv:
clang_tidy_args.extend(argv[argv.index("--") :])
argv = argv[: argv.index("--")]
args = parser.parse_args(argv)
# Extract changed lines for each file.
filename = None
lines_by_file = {}
for line in sys.stdin:
match = re.search('^\+\+\+\ "?(.*?/){%s}([^ \t\n"]*)' % args.p, line)
if match:
filename = match.group(2)
if filename is None:
continue
if args.regex is not None:
if not re.match("^%s$" % args.regex, filename):
continue
else:
if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE):
continue
match = re.search("^@@.*\+(\d+)(,(\d+))?", line)
if match:
start_line = int(match.group(1))
line_count = 1
if match.group(3):
line_count = int(match.group(3))
if line_count == 0:
continue
end_line = start_line + line_count - 1
lines_by_file.setdefault(filename, []).append([start_line, end_line])
if not any(lines_by_file):
print("No relevant changes found.")
sys.exit(0)
max_task_count = args.j
if max_task_count == 0:
max_task_count = multiprocessing.cpu_count()
max_task_count = min(len(lines_by_file), max_task_count)
combine_fixes = False
export_fixes_dir = None
delete_fixes_dir = False
if args.export_fixes is not None:
# if a directory is given, create it if it does not exist
if args.export_fixes.endswith(os.path.sep) and not os.path.isdir(
args.export_fixes
):
os.makedirs(args.export_fixes)
if not os.path.isdir(args.export_fixes):
if not yaml:
raise RuntimeError(
"Cannot combine fixes in one yaml file. Either install PyYAML or specify an output directory."
)
combine_fixes = True
if os.path.isdir(args.export_fixes):
export_fixes_dir = args.export_fixes
if combine_fixes:
export_fixes_dir = tempfile.mkdtemp()
delete_fixes_dir = True
# Tasks for clang-tidy.
task_queue = queue.Queue(max_task_count)
# A lock for console output.
lock = threading.Lock()
# List of files with a non-zero return code.
failed_files = []
# Run a pool of clang-tidy workers.
start_workers(
max_task_count, run_tidy, (task_queue, lock, args.timeout, failed_files)
)
# Form the common args list.
common_clang_tidy_args = []
if args.fix:
common_clang_tidy_args.append("-fix")
if args.checks != "":
common_clang_tidy_args.append("-checks=" + args.checks)
if args.config_file != "":
common_clang_tidy_args.append("-config-file=" + args.config_file)
if args.quiet:
common_clang_tidy_args.append("-quiet")
if args.build_path is not None:
common_clang_tidy_args.append("-p=%s" % args.build_path)
if args.use_color:
common_clang_tidy_args.append("--use-color")
for arg in args.extra_arg:
common_clang_tidy_args.append("-extra-arg=%s" % arg)
for arg in args.extra_arg_before:
common_clang_tidy_args.append("-extra-arg-before=%s" % arg)
for plugin in args.plugins:
common_clang_tidy_args.append("-load=%s" % plugin)
for name in lines_by_file:
line_filter_json = json.dumps(
[{"name": name, "lines": lines_by_file[name]}], separators=(",", ":")
)
# Run clang-tidy on files containing changes.
command = [args.clang_tidy_binary]
command.append("-line-filter=" + line_filter_json)
if args.export_fixes is not None:
# Get a temporary file. We immediately close the handle so clang-tidy can
# overwrite it.
(handle, tmp_name) = tempfile.mkstemp(suffix=".yaml", dir=export_fixes_dir)
os.close(handle)
command.append("-export-fixes=" + tmp_name)
command.extend(common_clang_tidy_args)
command.append(name)
command.extend(clang_tidy_args)
task_queue.put(command)
# Application return code
return_code = 0
# Wait for all threads to be done.
task_queue.join()
# Application return code
return_code = 0
if failed_files:
return_code = 1
if combine_fixes:
print("Writing fixes to " + args.export_fixes + " ...")
try:
merge_replacement_files(export_fixes_dir, args.export_fixes)
except:
sys.stderr.write("Error exporting fixes.\n")
traceback.print_exc()
return_code = 1
if delete_fixes_dir:
shutil.rmtree(export_fixes_dir)
sys.exit(return_code)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,87 @@
// Append using posix-style a file name or directory to Base
function append(Base, New) {
if (!New)
return Base;
if (Base)
Base += "/";
Base += New;
return Base;
}
// Get relative path to access FilePath from CurrentDirectory
function computeRelativePath(FilePath, CurrentDirectory) {
var Path = FilePath;
while (Path) {
if (CurrentDirectory == Path)
return FilePath.substring(Path.length + 1);
Path = Path.substring(0, Path.lastIndexOf("/"));
}
var Dir = CurrentDirectory;
var Result = "";
while (Dir) {
if (Dir == FilePath)
break;
Dir = Dir.substring(0, Dir.lastIndexOf("/"));
Result = append(Result, "..")
}
Result = append(Result, FilePath.substring(Dir.length))
return Result;
}
function genLink(Ref, CurrentDirectory) {
var Path = computeRelativePath(Ref.Path, CurrentDirectory);
if (Ref.RefType == "namespace")
Path = append(Path, "index.html");
else
Path = append(Path, Ref.Name + ".html")
ANode = document.createElement("a");
ANode.setAttribute("href", Path);
var TextNode = document.createTextNode(Ref.Name);
ANode.appendChild(TextNode);
return ANode;
}
function genHTMLOfIndex(Index, CurrentDirectory, IsOutermostList) {
// Out will store the HTML elements that Index requires to be generated
var Out = [];
if (Index.Name) {
var SpanNode = document.createElement("span");
var TextNode = document.createTextNode(Index.Name);
SpanNode.appendChild(genLink(Index, CurrentDirectory));
Out.push(SpanNode);
}
if (Index.Children.length == 0)
return Out;
// Only the outermost list should use ol, the others should use ul
var ListNodeName = IsOutermostList ? "ol" : "ul";
var ListNode = document.createElement(ListNodeName);
for (Child of Index.Children) {
var LiNode = document.createElement("li");
ChildNodes = genHTMLOfIndex(Child, CurrentDirectory, false);
for (Node of ChildNodes)
LiNode.appendChild(Node);
ListNode.appendChild(LiNode);
}
Out.push(ListNode);
return Out;
}
function createIndex(Index) {
// Get the DOM element where the index will be created
var IndexDiv = document.getElementById("sidebar-left");
// Get the relative path of this file
CurrentDirectory = IndexDiv.getAttribute("path");
var IndexNodes = genHTMLOfIndex(Index, CurrentDirectory, true);
for (Node of IndexNodes)
IndexDiv.appendChild(Node);
}
// Runs after DOM loads
document.addEventListener("DOMContentLoaded", function() {
// JsonIndex is a variable from another file that contains the index
// in JSON format
var Index = JSON.parse(JsonIndex);
createIndex(Index);
});

View File

@ -0,0 +1,133 @@
#!/usr/bin/env python
#
# =- run-find-all-symbols.py - Parallel find-all-symbols runner -*- python -*-=#
#
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#
# ===------------------------------------------------------------------------===#
"""
Parallel find-all-symbols runner
================================
Runs find-all-symbols over all files in a compilation database.
Example invocations.
- Run find-all-symbols on all files in the current working directory.
run-find-all-symbols.py <source-file>
Compilation database setup:
http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html
"""
import argparse
import json
import multiprocessing
import os
import Queue
import shutil
import subprocess
import sys
import tempfile
import threading
def find_compilation_database(path):
"""Adjusts the directory until a compilation database is found."""
result = "./"
while not os.path.isfile(os.path.join(result, path)):
if os.path.realpath(result) == "/":
print("Error: could not find compilation database.")
sys.exit(1)
result += "../"
return os.path.realpath(result)
def MergeSymbols(directory, args):
"""Merge all symbol files (yaml) in a given directory into a single file."""
invocation = [args.binary, "-merge-dir=" + directory, args.saving_path]
subprocess.call(invocation)
print("Merge is finished. Saving results in " + args.saving_path)
def run_find_all_symbols(args, tmpdir, build_path, queue):
"""Takes filenames out of queue and runs find-all-symbols on them."""
while True:
name = queue.get()
invocation = [args.binary, name, "-output-dir=" + tmpdir, "-p=" + build_path]
sys.stdout.write(" ".join(invocation) + "\n")
subprocess.call(invocation)
queue.task_done()
def main():
parser = argparse.ArgumentParser(
description="Runs find-all-symbols over all" "files in a compilation database."
)
parser.add_argument(
"-binary",
metavar="PATH",
default="./bin/find-all-symbols",
help="path to find-all-symbols binary",
)
parser.add_argument(
"-j", type=int, default=0, help="number of instances to be run in parallel."
)
parser.add_argument(
"-p", dest="build_path", help="path used to read a compilation database."
)
parser.add_argument(
"-saving-path", default="./find_all_symbols_db.yaml", help="result saving path"
)
args = parser.parse_args()
db_path = "compile_commands.json"
if args.build_path is not None:
build_path = args.build_path
else:
build_path = find_compilation_database(db_path)
tmpdir = tempfile.mkdtemp()
# Load the database and extract all files.
database = json.load(open(os.path.join(build_path, db_path)))
files = [entry["file"] for entry in database]
# Filter out .rc files on Windows. CMake includes them for some reason.
files = [f for f in files if not f.endswith(".rc")]
max_task = args.j
if max_task == 0:
max_task = multiprocessing.cpu_count()
try:
# Spin up a bunch of tidy-launching threads.
queue = Queue.Queue(max_task)
for _ in range(max_task):
t = threading.Thread(
target=run_find_all_symbols, args=(args, tmpdir, build_path, queue)
)
t.daemon = True
t.start()
# Fill the queue with files.
for name in files:
queue.put(name)
# Wait for all threads to be done.
queue.join()
MergeSymbols(tmpdir, args)
except KeyboardInterrupt:
# This is a sad hack. Unfortunately subprocess goes
# bonkers with ctrl-c and we start forking merrily.
print("\nCtrl-C detected, goodbye.")
os.kill(0, 9)
if __name__ == "__main__":
main()