commit a30a89882f7d3eae0e3c06cd67548c9340bef10f
parent acb8670892098f6c444bb09cdc2f82d1abaf6a7f
Author: Ember Sawady <ecs@d2evs.net>
Date: Wed, 16 Aug 2023 22:21:44 +0000
drop hare release
Signed-off-by: Ember Sawady <ecs@d2evs.net>
Diffstat:
4 files changed, 0 insertions(+), 419 deletions(-)
diff --git a/Makefile b/Makefile
@@ -29,7 +29,6 @@ hare_srcs = \
./cmd/hare/main.ha \
./cmd/hare/plan.ha \
./cmd/hare/progress.ha \
- ./cmd/hare/release.ha \
./cmd/hare/schedule.ha \
./cmd/hare/subcmds.ha \
./cmd/hare/target.ha
diff --git a/cmd/hare/main.ha b/cmd/hare/main.ha
@@ -41,11 +41,6 @@ const help: []getopt::help = [
('X', "tags...", "unset build tags"),
"<path|module>",
]: []getopt::help),
- ("release", [
- "prepares a new release for a program or library",
- ('d', "enable dry-run mode; do not perform any changes"),
- "<major|minor|patch|x.y.z>",
- ]: []getopt::help),
("run", [
"compiles and runs the Hare program at <path>",
('v', "print executed commands"),
@@ -90,8 +85,6 @@ export fn main() void = {
yield &cache;
case "deps" =>
yield &deps;
- case "release" =>
- yield &release;
case "run" =>
yield &run;
case "test" =>
diff --git a/cmd/hare/release.ha b/cmd/hare/release.ha
@@ -1,361 +0,0 @@
-// License: GPL-3.0
-// (c) 2021-2022 Drew DeVault <sir@cmpwn.com>
-// (c) 2021 Ember Sawady <ecs@d2evs.net>
-// (c) 2022 Jon Eskin <eskinjp@gmail.com>
-use bufio;
-use errors;
-use fmt;
-use fs;
-use io;
-use os::exec;
-use os;
-use path;
-use strconv;
-use strings;
-use temp;
-
-type increment = enum {
- MAJOR,
- MINOR,
- PATCH,
-};
-
-type modversion = (uint, uint, uint);
-type git_error = !exec::exit_status;
-type badversion = !void;
-type release_error = !(exec::error | io::error | fs::error | errors::error |
- badversion | git_error);
-
-const changelog_template: str = "# This is the changelog for your release. It has been automatically pre-filled
-# with the changes from version {} via git-shortlog(1). Please review the
-# changelog below and, if necessary, add a brief note regarding any steps
-# required for the user to upgrade this software. It is recommended to keep this
-# brief and clinical, so readers can quickly understand what's changed, and to
-# save marketing comments for a separate release announcement.
-#
-# Any lines which begin with \"#\", like this one, are for your information
-# only, and will be removed from the final changelog. Edit this file to your
-# satisfaction, then save and close your editor.
-#
-{} version {}
-";
-
-const initial_template: str = "# These are the release notes for the initial release of {0}.
-#
-# Any lines which begin with \"#\", like this one, are for your information
-# only, and will be removed from the final release notes. Edit this file to your
-# satisfaction, then save and close your editor.
-#
-{0} version {1}
-";
-
-fn parseversion(in: str) (modversion | badversion) = {
- const items = strings::split(in, ".");
- defer free(items);
- if (len(items) != 3) {
- return badversion;
- };
- let major = 0u, minor = 0u, patch = 0u;
- const ptrs = [&major, &minor, &patch];
- for (let i = 0z; i < len(items); i += 1) {
- *ptrs[i] = match (strconv::stou(items[i])) {
- case let u: uint =>
- yield u;
- case =>
- return badversion;
- };
- };
- return (major, minor, patch);
-};
-
-fn do_release(
- next: (increment | modversion),
- dryrun: bool,
-) (void | release_error) = {
- // XXX: If we were feeling REALLY fancy we could run the diff and
- // automatically detect new functions/types/etc (minor bump), breaking
- // changes (major bump), or neither (patch bump). I don't feel that
- // fancy, however.
-
- // TODO: Run hare test
- checkbranch()?;
- checkstatus()?;
- git_runcmd("fetch")?;
- checkbehind()?;
-
- // TODO: Detect if distance from the last tag is zero commits
- const lasttag = match (git_readcmd("describe", "--abbrev=0")) {
- case git_error =>
- return do_initial_release(next);
- case let err: release_error =>
- return err;
- case let s: str =>
- yield strings::rtrim(s);
- };
- defer free(lasttag);
-
- const key = choosekey()?;
- defer free(key);
-
- const current = parseversion(lasttag)?;
- const new = nextversion(current, next);
- const newtag = fmt::asprintf("{}.{}.{}", new.0, new.1, new.2);
- defer free(newtag);
- const range = fmt::asprintf("{}..HEAD", lasttag);
- defer free(range);
-
- const name = path::basename(os::getcwd());
- const dir = temp::dir();
- defer os::rmdirall(dir)!;
- const (clfile, changelog) = temp::named(os::cwd,
- dir, io::mode::WRITE, 0o644)?;
- defer io::close(clfile)!;
- fmt::fprintfln(clfile, changelog_template, lasttag, name, newtag)?;
- shortlog(clfile, range)?;
-
- git_runcmd("tag", "-aeF", changelog, newtag)?;
- signtag(dir, name, newtag, key)?;
- fmt::printfln("Tagged {} version {}. "
- "Use 'git push --follow-tags' to publish the new release.",
- name, newtag)!;
-};
-
-fn do_initial_release(ver: (modversion | increment)) (void | release_error) = {
- const ver = match (ver) {
- case let ver: modversion =>
- yield ver;
- case increment =>
- fmt::errorln("Error: cannot increment version number without a previous version to reference.")!;
- fmt::errorln("For the first release, try 'hare release 1.0.0' instead.")!;
- os::exit(1);
- };
-
- const key = choosekey()?;
- defer free(key);
- const newtag = fmt::asprintf("{}.{}.{}", ver.0, ver.1, ver.2);
- defer free(newtag);
-
- const name = path::basename(os::getcwd());
- const dir = temp::dir();
- defer os::rmdirall(dir)!;
- const (clfile, changelog) = temp::named(os::cwd,
- dir, io::mode::WRITE, 0o644)?;
- defer io::close(clfile)!;
- fmt::fprintfln(clfile, initial_template, name, newtag)?;
-
- git_runcmd("tag", "-aeF", changelog, newtag)?;
- signtag(dir, name, newtag, key)?;
- fmt::printfln("Tagged {} version {}. "
- "Use 'git push --follow-tags' to publish the new release.",
- name, newtag)!;
-};
-
-fn nextversion(
- current: modversion,
- next: (increment | modversion),
-) modversion = {
- const next = match (next) {
- case let incr: increment =>
- yield incr;
- case let ver: modversion =>
- return ver;
- };
- switch (next) {
- case increment::MAJOR =>
- return (current.0 + 1, 0, 0);
- case increment::MINOR =>
- return (current.0, current.1 + 1, 0);
- case increment::PATCH =>
- return (current.0, current.1, current.2 + 1);
- };
-};
-
-fn checkbranch() (void | release_error) = {
- const default_branch = get_defaultbranch()?;
- defer free(default_branch);
- const current_branch = get_currentbranch()?;
- defer free(current_branch);
- if (default_branch != current_branch) {
- fmt::errorfln(
- "Warning! You do not have the {} branch checked out.",
- default_branch)!;
- };
-};
-
-fn checkstatus() (void | release_error) = {
- const status = strings::rtrim(git_readcmd("status", "-zuno")?);
- defer free(status);
- if (len(status) != 0) {
- fmt::errorln("Warning! You have uncommitted changes.")!;
- };
-};
-
-fn checkbehind() (void | release_error) = {
- const upstream = match (git_readcmd("rev-parse", "HEAD@{upstream}")) {
- case git_error =>
- // Fails if there is no upstream, in which case we don't need to
- // bother checking.
- return;
- case let err: release_error =>
- return err;
- case let s: str =>
- yield s;
- };
- defer free(upstream);
- const head = git_readcmd("rev-parse", "HEAD")?;
- defer free(head);
- if (upstream == head) {
- return;
- };
- match (git_runcmd("merge-base", "--is-ancestor", "HEAD@{upstream}", "HEAD")) {
- case git_error =>
- fmt::errorln("Warning! Your local branch is behind the upstream branch.")!;
- case let err: release_error =>
- return err;
- case => void;
- };
-};
-
-fn shortlog(out: io::file, what: str) (void | release_error) = {
- const cmd = exec::cmd("git", "shortlog", "--no-merges", what)?;
- exec::addfile(&cmd, os::stdout_file, out);
- const proc = exec::start(&cmd)?;
- const status = exec::wait(&proc)?;
- exec::check(&status)?;
-};
-
-fn choosekey() (str | release_error) = {
- match (os::getenv("HAREKEY")) {
- case void => void;
- case let name: str =>
- return name;
- };
-
- const paths = [
- "id_ed25519",
- "id_ecdsa",
- "id_rsa",
- "id_dsa",
- ];
- let buf = path::init()!;
- const home = os::getenv("HOME") as str;
- for (let i = 0z; i < len(paths); i += 1) {
- const cand = path::set(&buf, home, ".ssh", paths[i])!;
- if (os::stat(cand) is fs::error) {
- continue;
- };
- return strings::dup(cand);
- };
- fmt::errorln("No suitable SSH key found to sign releases with.")!;
-
- fmt::error("Would you like to generate one now? [Y/n] ")!;
- const line = match (bufio::scanline(os::stdin)?) {
- case io::EOF =>
- fmt::fatal("No suitable key available. Terminating.");
- case let line: []u8 =>
- yield strings::fromutf8(line)!;
- };
- defer free(line);
- if (line != "" && line != "y" && line != "Y") {
- fmt::fatal("No suitable key available. Terminating.");
- };
-
- const parent = path::set(&buf, home, ".ssh")!;
- os::mkdirs(parent, 0o755)?;
-
- const path = path::set(&buf, home, ".ssh", "id_ed25519")!;
- const cmd = match (exec::cmd("ssh-keygen", "-t", "ed25519", "-f", path)) {
- case let cmd: exec::command =>
- yield cmd;
- case let err: exec::error =>
- fmt::fatal("ssh-keygen: command not found. Is openssh installed?");
- };
- const proc = exec::start(&cmd)?;
- const status = exec::wait(&proc)?;
- exec::check(&status)?;
- fmt::println("You will be prompted to enter your password again to create the release signature.")!;
- return strings::dup(path);
-};
-
-fn signtag(tmpdir: str, name: str, tag: str, key: str) (void | release_error) = {
- // This could work without the agent if it were not for the fact that
- // ssh-keygen is bloody stupid when it comes to prompting you for your
- // password.
- let buf = path::init()!;
- const socket = path::set(&buf, tmpdir, "agent")!;
- const agent = exec::cmd("ssh-agent", "-Da", socket)?;
- exec::nullstd(&agent);
- const agent = exec::start(&agent)?;
- defer exec::kill(agent)!;
-
- const addkey = exec::cmd("ssh-add", key)?;
- exec::setenv(&addkey, "SSH_AUTH_SOCK", socket)!;
- const addkey = exec::start(&addkey)?;
- const addkey = exec::wait(&addkey)?;
- exec::check(&addkey)?;
-
- const prefix = fmt::asprintf("--prefix={}-{}/", name, tag);
- defer free(prefix);
- const archive = exec::cmd("git", "archive",
- "--format=tar.gz", prefix, tag)?;
- const ssh = exec::cmd("ssh-keygen",
- "-Y", "sign", "-f", key, "-n", "file")?;
- const note = exec::cmd("git", "notes", "add", "-F", "-", tag)?;
- exec::setenv(¬e, "GIT_NOTES_REF", "refs/notes/signatures/tar.gz")!;
-
- exec::setenv(&ssh, "SSH_AUTH_SOCK", socket)!;
- // Squelch "Signing data on standard input" message
- // TODO: It might be better to capture this and print it to stderr
- // ourselves if ssh-keygen exits nonzero, so that the error details are
- // available to the user for diagnosis.
- exec::addfile(&ssh, os::stderr_file, exec::nullfd);
-
- const pipe1 = exec::pipe();
- const pipe2 = exec::pipe();
- exec::addfile(&archive, os::stdout_file, pipe1.1);
- exec::addfile(&ssh, os::stdin_file, pipe1.0);
- exec::addfile(&ssh, os::stdout_file, pipe2.1);
- exec::addfile(¬e, os::stdin_file, pipe2.0);
- const archive = exec::start(&archive)?;
- const ssh = exec::start(&ssh)?;
- const note = exec::start(¬e)?;
- io::close(pipe1.0)?;
- io::close(pipe1.1)?;
- io::close(pipe2.0)?;
- io::close(pipe2.1)?;
- exec::check(&exec::wait(&archive)?)?;
- exec::check(&exec::wait(&ssh)?)?;
- exec::check(&exec::wait(¬e)?)?;
-};
-
-fn git_runcmd(args: str...) (void | release_error) = {
- const cmd = exec::cmd("git", args...)?;
- exec::addfile(&cmd, os::stderr_file, exec::nullfd);
- const proc = exec::start(&cmd)?;
- const status = exec::wait(&proc)?;
- return exec::check(&status)?;
-};
-
-fn git_readcmd(args: str...) (str | release_error) = {
- const pipe = exec::pipe();
- defer io::close(pipe.0)!;
- const cmd = exec::cmd("git", args...)?;
- exec::addfile(&cmd, os::stdout_file, pipe.1);
- exec::addfile(&cmd, os::stderr_file, exec::nullfd);
- const proc = exec::start(&cmd)?;
- io::close(pipe.1)?;
- const result = io::drain(pipe.0)?;
- const status = exec::wait(&proc)?;
- exec::check(&status)?;
- return strings::fromutf8(result)!;
-};
-
-fn get_defaultbranch() (str | release_error) = {
- const branch = git_readcmd("config",
- "--default", "master", "init.defaultBranch")?;
- return strings::rtrim(branch);
-};
-
-fn get_currentbranch() (str | release_error) = {
- return strings::rtrim(git_readcmd("branch", "--show-current")?);
-};
diff --git a/cmd/hare/subcmds.ha b/cmd/hare/subcmds.ha
@@ -305,56 +305,6 @@ fn deps(cmd: *getopt::command) void = {
};
};
-fn release(cmd: *getopt::command) void = {
- let dryrun = false;
- for (let i = 0z; i < len(cmd.opts); i += 1) {
- let opt = cmd.opts[i];
- switch (opt.0) {
- case 'd' =>
- dryrun = true;
- case => abort();
- };
- };
-
- if (len(cmd.args) == 0) {
- getopt::printusage(os::stderr, "release", cmd.help)!;
- os::exit(1);
- };
-
- const next = switch (cmd.args[0]) {
- case "major" =>
- yield increment::MAJOR;
- case "minor" =>
- yield increment::MINOR;
- case "patch" =>
- yield increment::PATCH;
- case =>
- yield match (parseversion(cmd.args[0])) {
- case badversion =>
- getopt::printusage(os::stderr, "release", cmd.help)!;
- os::exit(1);
- case let ver: modversion =>
- yield ver;
- };
- };
-
- match (do_release(next, dryrun)) {
- case void => void;
- case let err: exec::error =>
- fmt::fatal(exec::strerror(err));
- case let err: errors::error =>
- fmt::fatal(errors::strerror(err));
- case let err: io::error =>
- fmt::fatal(io::strerror(err));
- case let err: fs::error =>
- fmt::fatal(fs::strerror(err));
- case let err: git_error =>
- fmt::fatal("git:", exec::exitstr(err));
- case badversion =>
- fmt::fatal("Error: invalid format string. Hare uses semantic versioning, in the form major.minor.patch.");
- };
-};
-
fn run(cmd: *getopt::command) void = {
const build_target = default_target();
let tags = module::tags_dup(build_target.tags);