Skip to content

Commit abfbc72

Browse files
committed
crate-features: Added a tool to summarise unstable Rust feature use
See #13 Signed-off-by: SlyMarbo <[email protected]>
1 parent e49d92f commit abfbc72

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

tools/crate-features/BUILD.bazel

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Copyright 2022 The Firefly Authors.
2+
#
3+
# Use of this source code is governed by a BSD 3-clause
4+
# license that can be found in the LICENSE file.
5+
6+
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
7+
load("//bazel/gofmt:gofmt.bzl", "gofmt")
8+
9+
go_library(
10+
name = "crate-features_lib",
11+
srcs = [
12+
"main.go",
13+
],
14+
importpath = "firefly-os.dev/tools/crate-features",
15+
visibility = ["//visibility:private"],
16+
)
17+
18+
gofmt(
19+
name = "gofmt",
20+
embed = [":crate-features_lib"],
21+
)
22+
23+
go_binary(
24+
name = "crate-features",
25+
embed = [":crate-features_lib"],
26+
visibility = ["//visibility:public"],
27+
)

tools/crate-features/main.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// Copyright 2022 The Firefly Authors.
2+
//
3+
// Use of this source code is governed by a BSD 3-clause
4+
// license that can be found in the LICENSE file.
5+
6+
// Command crate-features analyses the Rust crates in the
7+
// repository for unstable features, printing a summary.
8+
//
9+
package main
10+
11+
import (
12+
"bufio"
13+
"fmt"
14+
"io/fs"
15+
"log"
16+
"os"
17+
"path"
18+
"sort"
19+
"strings"
20+
"text/tabwriter"
21+
)
22+
23+
// Features parses the given file, returning the set
24+
// of unstable Rust features the crate requires.
25+
//
26+
func Features(fsys fs.FS, name string) ([]string, error) {
27+
f, err := fsys.Open(name)
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
defer f.Close()
33+
34+
found := make(map[string]bool)
35+
s := bufio.NewScanner(f)
36+
for s.Scan() {
37+
const (
38+
featurePrefix = "#![feature("
39+
featureSuffix = ")]"
40+
)
41+
42+
line := s.Text()
43+
if !strings.HasPrefix(line, featurePrefix) {
44+
continue
45+
}
46+
47+
features := strings.TrimPrefix(line, featurePrefix)
48+
49+
// Drop any trailing line comment.
50+
if i := strings.Index(features, "//"); i > 0 {
51+
features = strings.TrimSpace(features[:i])
52+
}
53+
54+
if !strings.HasSuffix(features, featureSuffix) {
55+
log.Printf("WARN: %s: malformed feature inner attribute line: %q", name, line)
56+
continue
57+
}
58+
59+
features = strings.TrimSuffix(features, featureSuffix)
60+
for _, feature := range strings.Split(features, ",") {
61+
found[strings.TrimSpace(feature)] = true
62+
}
63+
}
64+
65+
if err := s.Err(); err != nil {
66+
return nil, err
67+
}
68+
69+
if len(found) == 0 {
70+
return nil, nil
71+
}
72+
73+
features := make([]string, 0, len(found))
74+
for feature := range found {
75+
features = append(features, feature)
76+
}
77+
78+
sort.Strings(features)
79+
80+
return features, nil
81+
}
82+
83+
func init() {
84+
log.SetFlags(0)
85+
log.SetOutput(os.Stderr)
86+
log.SetPrefix("")
87+
}
88+
89+
func main() {
90+
// If we're being run with `bazel run`, we're in
91+
// a semi-random build directory, and need to move
92+
// to the workspace root directory.
93+
//
94+
workspace := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
95+
if workspace != "" {
96+
err := os.Chdir(workspace)
97+
if err != nil {
98+
log.Printf("Failed to change directory to %q: %v", workspace, err)
99+
}
100+
}
101+
102+
features := make(map[string][]string)
103+
fsys := os.DirFS(".")
104+
err := fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
105+
if err != nil {
106+
return err
107+
}
108+
109+
if d.IsDir() {
110+
return nil
111+
}
112+
113+
if path.Base(name) != "lib.rs" {
114+
return nil
115+
}
116+
117+
found, err := Features(fsys, name)
118+
if err != nil {
119+
return err
120+
}
121+
122+
crate := path.Dir(name)
123+
if path.Base(crate) == "src" {
124+
crate = path.Dir(crate)
125+
}
126+
127+
for _, feature := range found {
128+
features[feature] = append(features[feature], crate)
129+
}
130+
131+
return nil
132+
})
133+
134+
if err != nil {
135+
log.Fatalf("Failed to scan repository: %v", err)
136+
}
137+
138+
sorted := make([]string, 0, len(features))
139+
for feature, crates := range features {
140+
sorted = append(sorted, feature)
141+
sort.Strings(crates)
142+
}
143+
144+
sort.Strings(sorted)
145+
146+
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
147+
for _, feature := range sorted {
148+
fmt.Fprintf(w, "%s\t%s\n", feature, strings.Join(features[feature], ", "))
149+
}
150+
151+
err = w.Flush()
152+
if err != nil {
153+
log.Fatalf("Failed to print summary: %v", err)
154+
}
155+
}

0 commit comments

Comments
 (0)