Skip to content

Commit 3aaac8b

Browse files
committed
Add compiled unit source / input option #33 (#121)
1 parent ed5ada4 commit 3aaac8b

File tree

2 files changed

+190
-21
lines changed

2 files changed

+190
-21
lines changed

build.zig

Lines changed: 189 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,35 @@ pub fn builder(b: *std.Build, options: BuilderOptions) StepBuilder {
7575
.rules = .empty,
7676
.include_paths = .empty,
7777
.exclude_paths = .empty,
78+
.sources = .empty,
7879
.b = b,
7980
.optimize = options.optimize,
8081
.target = options.target orelse b.graph.host,
8182
};
8283
}
8384

85+
/// Represents a source that can be linted.
86+
pub const LintSource = union(enum) {
87+
/// e.g., library or executable.
88+
compiled_unit: struct {
89+
compile_step: *std.Build.Step.Compile,
90+
},
91+
92+
pub fn compiled(compile: *std.Build.Step.Compile) LintSource {
93+
return .{
94+
.compiled_unit = .{
95+
.compile_step = compile,
96+
},
97+
};
98+
}
99+
};
100+
84101
const StepBuilder = struct {
85102
rules: shims.ArrayList(BuiltRule),
103+
// TODO: Collapse paths and sources into one array for union `LintSource`.
86104
include_paths: shims.ArrayList(std.Build.LazyPath),
87105
exclude_paths: shims.ArrayList(std.Build.LazyPath),
106+
sources: shims.ArrayList(LintSource),
88107
b: *std.Build,
89108
target: std.Build.ResolvedTarget,
90109
optimize: std.builtin.OptimizeMode,
@@ -110,11 +129,27 @@ const StepBuilder = struct {
110129
) catch @panic("OOM");
111130
}
112131

132+
/// Adds a source to be linted (e.g., library or executable). Only inputs
133+
/// resolved to this source within the projects path will be linted.
134+
///
135+
/// If a source is not set then it falls back to linting the include paths.
136+
/// If no include paths are given then it falls back to linting all source
137+
/// files under the current working directory.
138+
pub fn addSource(self: *StepBuilder, source: LintSource) void {
139+
const arena = self.b.allocator;
140+
self.sources.append(arena, source) catch @panic("OOM");
141+
}
142+
113143
/// Set the paths to include or exclude when running the linter.
114144
///
115-
/// Include defaults to the current working directory. `zig-out` and
116-
/// `.zig-cache` are always excluded - you don't need to explicitly include
117-
/// them if setting exclude paths.
145+
/// Unless a source is set, includes defaults to the current working
146+
/// directory.
147+
///
148+
/// If a source is set then paths included here included in combination with
149+
/// the inputs resolved from the set source.
150+
///
151+
/// `zig-out` and `.zig-cache` are always excluded - you don't need to
152+
/// explicitly include them if setting exclude paths.
118153
pub fn addPaths(
119154
self: *StepBuilder,
120155
paths: struct {
@@ -132,17 +167,6 @@ const StepBuilder = struct {
132167

133168
pub fn build(self: *StepBuilder) *std.Build.Step {
134169
const b = self.b;
135-
const arena = b.allocator;
136-
137-
const include_paths = include_paths: {
138-
if (self.include_paths.items.len > 0) {
139-
break :include_paths self.include_paths.items;
140-
} else {
141-
var list = arena.alloc(std.Build.LazyPath, 1) catch @panic("OOM");
142-
list[0] = b.path("./");
143-
break :include_paths list;
144-
}
145-
};
146170

147171
return buildStep(
148172
b,
@@ -156,8 +180,9 @@ const StepBuilder = struct {
156180
.{},
157181
),
158182
},
159-
.include_paths = include_paths,
183+
.include_paths = self.include_paths.items,
160184
.exclude_paths = self.exclude_paths.items,
185+
.sources = self.sources.items,
161186
},
162187
);
163188
}
@@ -339,9 +364,17 @@ pub fn build(b: *std.Build) void {
339364

340365
const lint_cmd = b.step("lint", "Lint the linters own source code.");
341366
lint_cmd.dependOn(step: {
342-
const include_paths = shims.ArrayList(std.Build.LazyPath).empty;
367+
var sources = shims.ArrayList(LintSource).empty;
368+
sources.append(b.allocator, .compiled(b.addLibrary(.{
369+
.name = "zlinter",
370+
.root_module = zlinter_lib_module,
371+
}))) catch @panic("OOM");
372+
373+
var include_paths = shims.ArrayList(std.Build.LazyPath).empty;
343374
var exclude_paths = shims.ArrayList(std.Build.LazyPath).empty;
344375

376+
// Also lint all files within project, not just those resolved to our compiled source.
377+
include_paths.append(b.allocator, b.path("./")) catch @panic("OOM");
345378
exclude_paths.append(b.allocator, b.path("integration_tests/test_cases")) catch @panic("OOM");
346379
exclude_paths.append(b.allocator, b.path("integration_tests/src/test_case_references.zig")) catch @panic("OOM");
347380

@@ -398,6 +431,7 @@ pub fn build(b: *std.Build) void {
398431
),
399432
},
400433
.{
434+
.sources = sources.items,
401435
.target = target,
402436
.optimize = optimize,
403437
.include_paths = include_paths.items,
@@ -469,6 +503,7 @@ fn buildStep(
469503
},
470504
include_paths: []const std.Build.LazyPath,
471505
exclude_paths: []const std.Build.LazyPath,
506+
sources: []const LintSource,
472507
},
473508
) *std.Build.Step {
474509
const zlinter_lib_module: *std.Build.Module, const exe_file: std.Build.LazyPath, const build_rules_exe_file: std.Build.LazyPath = switch (options.zlinter) {
@@ -521,6 +556,7 @@ fn buildStep(
521556
zlinter_exe,
522557
options.include_paths,
523558
options.exclude_paths,
559+
options.sources,
524560
);
525561

526562
return &zlinter_run.step;
@@ -727,6 +763,9 @@ const ZlinterRun = struct {
727763
/// Exclude paths confiured within the build file.
728764
exclude_paths: []const std.Build.LazyPath,
729765

766+
/// The sources to lint (e.g., an executable or library).
767+
sources: []const LintSource,
768+
730769
const Arg = union(enum) {
731770
artifact: *std.Build.Step.Compile,
732771
bytes: []const u8,
@@ -737,6 +776,7 @@ const ZlinterRun = struct {
737776
exe: *std.Build.Step.Compile,
738777
include_paths: []const std.Build.LazyPath,
739778
exclude_paths: []const std.Build.LazyPath,
779+
sources: []const LintSource,
740780
) *ZlinterRun {
741781
const arena = owner.allocator;
742782

@@ -751,6 +791,7 @@ const ZlinterRun = struct {
751791
.argv = .empty,
752792
.exclude_paths = exclude_paths,
753793
.include_paths = include_paths,
794+
.sources = sources,
754795
};
755796

756797
for (include_paths) |path| {
@@ -772,6 +813,14 @@ const ZlinterRun = struct {
772813
const bin_file = exe.getEmittedBin();
773814
bin_file.addStepDependencies(&self.step);
774815

816+
for (sources) |s| {
817+
switch (s) {
818+
.compiled_unit => |info| {
819+
self.step.dependOn(&info.compile_step.step);
820+
},
821+
}
822+
}
823+
775824
return self;
776825
}
777826

@@ -784,14 +833,16 @@ const ZlinterRun = struct {
784833
fn subPaths(
785834
step: *std.Build.Step,
786835
paths: []const std.Build.LazyPath,
787-
) error{OutOfMemory}![]const []const u8 {
836+
) error{OutOfMemory}!?[]const []const u8 {
837+
if (paths.len == 0) return null;
838+
788839
const b = step.owner;
789840

790841
var list: shims.ArrayList([]const u8) = try .initCapacity(
791842
b.allocator,
792843
paths.len,
793844
);
794-
errdefer list.deinit(b.allocator);
845+
defer list.deinit(b.allocator);
795846

796847
for (paths) |path| {
797848
list.appendAssumeCapacity(
@@ -807,9 +858,65 @@ const ZlinterRun = struct {
807858
const b = run.step.owner;
808859
const arena = b.allocator;
809860

861+
var cwd_buff: [std.fs.max_path_bytes]u8 = undefined;
862+
const cwd: BuildCwd = .init(&cwd_buff);
863+
864+
var includes: std.ArrayList(std.Build.LazyPath) = try .initCapacity(
865+
b.allocator,
866+
@max(1, run.include_paths.len),
867+
);
868+
defer includes.deinit(b.allocator);
869+
870+
includes.appendSliceAssumeCapacity(run.include_paths);
871+
872+
for (run.sources) |source| {
873+
switch (source) {
874+
.compiled_unit => |info| {
875+
var exe = info.compile_step;
876+
877+
// TODO: Use graph for import map.
878+
const graph = exe.root_module.getGraph();
879+
_ = graph;
880+
881+
var inputs = exe.step.inputs;
882+
std.debug.assert(inputs.populated());
883+
884+
var it = inputs.table.iterator();
885+
while (it.next()) |entry| {
886+
const p = entry.key_ptr.*;
887+
sub_paths: for (entry.value_ptr.items) |sub_path| {
888+
var buf: [std.fs.max_path_bytes]u8 = undefined;
889+
const joined_path = if (p.sub_path.len == 0) sub_path else p: {
890+
const fmt = "{s}" ++ std.fs.path.sep_str ++ "{s}";
891+
break :p std.fmt.bufPrint(
892+
&buf,
893+
fmt,
894+
.{ p.sub_path, sub_path },
895+
) catch {
896+
std.debug.print(
897+
"Warning: Name too long - " ++ fmt,
898+
.{ p.sub_path, sub_path },
899+
);
900+
continue :sub_paths;
901+
};
902+
};
903+
std.debug.assert(joined_path.len > 0);
904+
905+
if (cwd.relativePath(b, joined_path)) |path| {
906+
try includes.append(b.allocator, path);
907+
}
908+
}
909+
}
910+
},
911+
}
912+
}
913+
if (includes.items.len == 0) {
914+
includes.appendAssumeCapacity(b.path("./"));
915+
}
916+
810917
const build_info_zon_bytes: []const u8 = toZonString(BuildInfo{
811-
.include_paths = if (run.include_paths.len > 0) try subPaths(&run.step, run.include_paths) else null,
812-
.exclude_paths = if (run.exclude_paths.len > 0) try subPaths(&run.step, run.exclude_paths) else null,
918+
.include_paths = try subPaths(&run.step, includes.items),
919+
.exclude_paths = try subPaths(&run.step, run.exclude_paths),
813920
}, b.allocator);
814921

815922
const env_map = arena.create(std.process.EnvMap) catch @panic("OOM");
@@ -990,6 +1097,68 @@ fn readHtmlTemplate(b: *std.Build, path: std.Build.LazyPath) ![]const u8 {
9901097
return buffer;
9911098
}
9921099

1100+
/// Normalised representation of the current working directory
1101+
const BuildCwd = struct {
1102+
path: []const u8,
1103+
dir: std.fs.Dir,
1104+
1105+
pub fn init(buff: *[std.fs.max_path_bytes]u8) BuildCwd {
1106+
return .{
1107+
.dir = std.fs.cwd(),
1108+
.path = std.process.getCwd(buff) catch unreachable,
1109+
};
1110+
}
1111+
1112+
/// Returns a path relative to the current working directory or null if the
1113+
/// path is not relative to the current working directory.
1114+
///
1115+
/// If the path is a relative path, we check whether it exists with the
1116+
/// current working directory.
1117+
///
1118+
/// If the path is absolute, we check whether it resolves to a readable
1119+
/// file within the current working directory.
1120+
pub fn relativePath(
1121+
self: *const BuildCwd,
1122+
b: *std.Build,
1123+
to: []const u8,
1124+
) ?std.Build.LazyPath {
1125+
if (std.fs.path.isAbsolute(to)) {
1126+
const relative = std.fs.path.relative(
1127+
b.allocator,
1128+
self.path,
1129+
to,
1130+
) catch |e|
1131+
switch (e) {
1132+
error.OutOfMemory => @panic("OOM"),
1133+
error.Unexpected,
1134+
error.CurrentWorkingDirectoryUnlinked,
1135+
=> return null,
1136+
};
1137+
errdefer b.allocator.free(relative);
1138+
1139+
if (relative.len != 0 and isReadable(&self.dir, relative)) {
1140+
return b.path(relative);
1141+
} else {
1142+
b.allocator.free(relative);
1143+
return null;
1144+
}
1145+
} else {
1146+
if (isReadable(&self.dir, to)) {
1147+
return b.path(b.dupe(to));
1148+
}
1149+
}
1150+
return null;
1151+
}
1152+
1153+
fn isReadable(from: *const std.fs.Dir, sub_path: []const u8) bool {
1154+
_ = from.access(
1155+
sub_path,
1156+
.{ .mode = .read_only },
1157+
) catch return false;
1158+
return true;
1159+
}
1160+
};
1161+
9931162
const BuildInfo = @import("src/lib/BuildInfo.zig");
9941163
const std = @import("std");
9951164
const isLintableFilePath = @import("src/lib/files.zig").isLintableFilePath;

src/lib/session.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ pub const LintContext = struct {
248248
gpa: std.mem.Allocator,
249249
doc: *LintDocument,
250250
) !void {
251-
var mem: [4096]u8 = undefined;
251+
var mem: [std.fs.max_path_bytes]u8 = undefined;
252252
var fba = std.heap.FixedBufferAllocator.init(&mem);
253253
const uri = try zls.URI.fromPath(
254254
fba.allocator(),

0 commit comments

Comments
 (0)