Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ fn addTests(
const run = b.addRunArtifact(anyzig);
run.setName("anyzig init (no version)");
run.addArg("init");
run.expectStdErrEqual("error: anyzig init requires a version, i.e. 'zig 0.13.0 init'\n");
run.expectStdErrEqual("error: anyzig init requires a version, you can:\n" ++
" 1. run 'zig 0.13.0 init'\n" ++
" 2. set an ad-hoc version using 'zig any set-ad-hoc-version VERSION'\n");
test_step.dependOn(&run.step);
}

Expand Down Expand Up @@ -244,6 +246,7 @@ fn addTests(
) });
t.run.addCheck(.{ .expect_stderr_match = "zig any version" });
t.run.addCheck(.{ .expect_stderr_match = "zig any set-verbosity" });
t.run.addCheck(.{ .expect_stderr_match = "zig any set-ad-hoc-version" });
}

{
Expand Down Expand Up @@ -298,6 +301,48 @@ fn addTests(
});
}

{
const t = test_factory.add(.{
.name = "test-any-set-ad-hoc-version-none",
.input_dir = .no_input,
.options = .nosetup,
.args = &.{ "any", "set-ad-hoc-version" },
});
t.run.expectStdErrEqual("anyzig: error: missing VERSION\n");
}

{
const t = test_factory.add(.{
.name = "test-any-set-ad-hoc-version-too-many",
.input_dir = .no_input,
.options = .nosetup,
.args = &.{ "any", "set-ad-hoc-version", "0.14.0", "0.15.2" },
});
t.run.expectStdErrEqual("anyzig: error: too many cmdline args\n");
}

{
const t = test_factory.add(.{
.name = "test-any-set-ad-hoc-version-bad",
.input_dir = .no_input,
.options = .nosetup,
.args = &.{ "any", "set-ad-hoc-version", "whattheheck" },
});
t.run.expectStdErrEqual("anyzig: error: invalid VERSION 'whattheheck'\n");
}

{
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// TODO: override the appdata directory to run this test
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// _ = test_factory.add(.{
// .name = "test-any-set-ad-hoc-version-warn",
// .input_dir = .no_input,
// .options = .nosetup,
// .args = &.{ "any", "set-ad-hoc-version", "0.14.0" },
// });
}

_ = test_factory.add(.{
.name = "test-master-version",
.input_dir = .no_input,
Expand Down
101 changes: 87 additions & 14 deletions src/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,35 @@ fn readVerbosityFile() union(enum) {
);
}

fn readAdHocVersionFile() ?VersionSpecifier {
const app_data_dir = global.getAppDataDir() catch return null;
const ad_hoc_version_path = std.fs.path.join(global.arena, &.{ app_data_dir, "ad-hoc-version" }) catch |e| oom(e);
defer global.arena.free(ad_hoc_version_path);
const content: []u8 = read_file: {
const file = std.fs.cwd().openFile(ad_hoc_version_path, .{}) catch |err| switch (err) {
error.FileNotFound => return null,
else => |e| std.debug.panic("open '{s}' failed with {s}", .{ ad_hoc_version_path, @errorName(e) }),
};
defer file.close();
break :read_file file.readToEndAlloc(global.arena, std.math.maxInt(usize)) catch |err| std.debug.panic(
"read '{s}' failed with {s}",
.{ ad_hoc_version_path, @errorName(err) },
);
};
defer global.arena.free(content);
const content_trimmed = std.mem.trimRight(u8, content, &std.ascii.whitespace);
if (VersionSpecifier.parse(content_trimmed)) |parsed_version| {
return parsed_version;
}

std.debug.panic(
"file '{s}' had the following unexpected content:\n" ++
"---\n{s}\n---\n" ++
"we expect the content to be a valid semantic version (e.g. 'master', '0.14.0')",
.{ ad_hoc_version_path, content },
);
}

fn anyzigLog(
comptime level: std.log.Level,
comptime scope: @Type(.enum_literal),
Expand Down Expand Up @@ -355,25 +384,39 @@ pub fn main() !void {
} else break :blk_is_help false;
};

// manual version gets priority over ad hoc version
if (manual_version) |version| break :blk .{ version, !is_help };
if (readAdHocVersionFile()) |version| break :blk .{ version, !is_help };
try std.io.getStdErr().writer().print(
"error: anyzig init requires a version, i.e. 'zig 0.13.0 {s}'\n",
"error: anyzig init requires a version, you can:\n" ++
" 1. run 'zig 0.13.0 {s}'\n" ++
" 2. set an ad-hoc version using 'zig any set-ad-hoc-version VERSION'\n",
.{command},
);
std.process.exit(0xff);
}
if (std.mem.eql(u8, command, "any")) std.process.exit(try anyCommand(cmdline, cmdline_offset + 1));
}

// 1. use manual version if specified
if (manual_version) |version| break :blk .{ version, false };
const build_root = try findBuildRoot(arena, build_root_options) orelse {
try std.io.getStdErr().writeAll(
"no build.zig to pull a zig version from, you can:\n" ++
" 1. run '" ++ exe_str ++ " VERSION' to specify a version\n" ++
" 2. run from a directory where a build.zig can be found\n",
);
std.process.exit(0xff);
};
break :blk .{ .{ .semantic = try determineSemanticVersion(arena, build_root) }, false };

// 2. use project version (note: we intentionally fail if in a
// project without a specified version)
if (try findBuildRoot(arena, build_root_options)) |build_root| {
break :blk .{ .{ .semantic = try determineSemanticVersion(arena, build_root) }, false };
}

// 3. fall back to ad hoc version when outside a project
if (readAdHocVersionFile()) |version| break :blk .{ version, false };

try std.io.getStdErr().writeAll(
"no build.zig to pull a zig version from, you can:\n" ++
" 1. run '" ++ exe_str ++ " VERSION' to specify a version\n" ++
" 2. run from a directory where a build.zig can be found\n" ++
" 3. set an ad-hoc version using 'zig any set-ad-hoc-version VERSION'\n",
);
std.process.exit(0xff);
};

const app_data_path = try std.fs.getAppDataDir(arena, "anyzig");
Expand Down Expand Up @@ -545,10 +588,12 @@ fn anyCommandUsage() !u8 {
try std.io.getStdErr().writer().print(
"any" ++ @tagName(build_options.exe) ++ " {s} from https://github.com/marler8997/anyzig\n" ++
"Here are the anyzig-specific subcommands:\n" ++
" zig any set-verbosity LEVEL | sets the default system-wide verbosity\n" ++
" | accepts 'warn' or 'debug'\n" ++
" zig any version | print the version of anyzig to stdout\n" ++
" zig any list-installed | list all versions of zig installed in the global cache\n",
" zig any set-verbosity LEVEL | sets the default system-wide verbosity\n" ++
" | accepts 'warn' or 'debug'\n" ++
" zig any set-ad-hoc-version VERSION | sets the version to use by default when outside a project directory\n" ++
" | accepts a version specifier (e.g. 'master' or '0.15.2'\n" ++
" zig any version | print the version of anyzig to stdout\n" ++
" zig any list-installed | list all versions of zig installed in the global cache\n",
.{@embedFile("version")},
);
return 0xff;
Expand Down Expand Up @@ -594,6 +639,28 @@ fn anyCommand(cmdline: Cmdline, cmdline_offset: usize) !u8 {
.loaded_from_file => |l| std.debug.assert(l == level),
}
return 0;
} else if (std.mem.eql(u8, command, "set-ad-hoc-version")) {
if (arg_offset >= cmdline.len()) errExit("missing VERSION", .{});
if (arg_offset + 1 < cmdline.len()) errExit("too many cmdline args", .{});
const version_str = cmdline.arg(arg_offset);
const version: VersionSpecifier = if (VersionSpecifier.parse(version_str)) |v| v else errExit("invalid VERSION '{s}'", .{version_str});
{
const app_data_dir = try global.getAppDataDir();
const version_path = std.fs.path.join(global.arena, &.{ app_data_dir, "ad-hoc-version" }) catch |e| oom(e);
defer global.arena.free(version_path);
if (std.fs.path.dirname(version_path)) |dir| {
try std.fs.cwd().makePath(dir);
}
const file = try std.fs.cwd().createFile(version_path, .{});
defer file.close();
try file.writer().print("{s}\n", .{version_str});
}
if (readAdHocVersionFile()) |v| {
std.debug.assert(v.eql(version));
} else {
@panic("no ad hoc version file after writing it?");
}
return 0;
} else if (std.mem.eql(u8, command, "list-installed")) {
if (arg_offset < cmdline.len()) errExit("the 'list-installed' subcommand does not take any cmdline args", .{});
try listInstalled();
Expand Down Expand Up @@ -767,6 +834,12 @@ const VersionSpecifier = union(enum) {
.zls => return null,
};
}
pub fn eql(self: VersionSpecifier, other: VersionSpecifier) bool {
switch (self) {
.master => return other == .master,
.semantic => |s| return other == .semantic and s.eql(other.semantic),
}
}
};

const arch = switch (builtin.cpu.arch) {
Expand Down
Loading