Incorporate some git information into NetHack so that it
is potentially visible to a player. That's useful when
collecting details about the version that they are
running and, if the gitinfo is present, it can tie the
code to a specific git commit in the repository.
This modifies 'makedefs -v' to check for the presence of a data file
called dat/gitinfo.txt and if it is there, parse out its
contents, then write additional lines to include/date.h beyond
what 'makedefs -v' was previously putting in there, similar to
this sample:
#define NETHACK_GIT_SHA "
0c84e564c78e2024e562d39539376ce2e21eec8e"
#define NETHACK_GIT_BRANCH "NetHack-3.6.0"
The contents of an appropriate dat/gitinfo.txt are as follows,
and trailing/leading whitespace is not significant:
githash =
0c84e564c78e2024e562d39539376ce2e21eec8e
gitbranch = NetHack-3.6.0
It also adjusts the contents of the 'v' version information to
include the additional git info when available.
Also adds some hooks DEVEL/hooksdir and a perl file to DEVEL
for simplifying and automating the deposit of dat/gitinfo.txt
so that it generally reflects the most current git commit.
DEVEL/gitinfo.pl can be used to build dat/gitinfo.txt at any
time without doing a commit, merge, or checkout.
perl DEVEL/gitinfo.pl
command line --version and -version support
To complement the extra information being provided in the
version by the 'v' command, this also adds support for the
following new command line arguments:
--version
-version Output the NetHack version string then exit.
--version:paste Output the NetHack version string and also copy it to
-version:paste the platform's paste buffer for insertion somewhere,
then exit.
If the paste variation of -version is requested on a platform that
hasn't incorporated any support for the capability, it will deliver
the version info then an error message, prior to exiting.
To support the extended -version:paste variation, a port needs to:
- provide a port-specific routine to perform
the paste buffer copy in a port code file.
- #define RUNTIME_PASTEBUF_SUPPORT in the include/portconf.h header file.
--skeleton--
void port_insert_pastebuf(buf)
char *buf;
{
/* insert code to copy the version info from buf into
platform's paste buffer in a supported way */
}
macosx and Windows have both added support for RUNTIME_PASTEBUF_SUPPORT
--- /dev/null
+#!/usr/bin/perl
+
+#STARTUP-START
+BEGIN {
+ # OS hackery has to be duplicated in each of the hooks :/
+ # first the directory separator
+ my $DS = quotemeta('/');
+ my $PDS = '/';
+ # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+ # temporarily removed because inconsistent behavior
+ # if ($^O eq "msys")
+ # {
+ # $/ = "\r\n";
+ # $\ = "\r\n";
+ # }
+ if($^O eq "MSWin32"){
+ $DS = quotemeta('\\');
+ $PDS = '\\';
+ }
+ $gitdir = `git rev-parse --git-dir`;
+ chomp $gitdir;
+ push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+
+&NHgithook::nhversioning;
&do_hook("POST");
}
+###
+### store githash and gitbranch in dat/gitinfo.txt
+###
+
+sub nhversioning {
+ use strict;
+ use warnings;
+
+ my $git_sha = `git rev-parse HEAD`;
+ $git_sha =~ s/\s+//g;
+ my $git_branch = `git rev-parse --abbrev-ref HEAD`;
+ $git_branch =~ s/\s+//g;
+
+ if (open my $fh, '<', 'dat/gitinfo.txt') {
+ while(my $line = <$fh>) {
+ if ((index $line, $git_sha) >= 0) {
+ close $fh;
+ print "No update made to dat/gitinfo.txt, existing githash=".$git_sha."\n";
+ return;
+ }
+ }
+ close $fh;
+ }
+ if (open my $fh, '>', 'dat/gitinfo.txt') {
+ print $fh 'githash='.$git_sha."\n";
+ print $fh 'gitbranch='.$git_branch."\n";
+ print "An updated dat/gitinfo.txt was written, githash=".$git_sha."\n";
+ }
+}
+
# PRIVATE
sub do_hook {
my($p) = @_;
#STARTUP-END
&NHgithook::PRE;
+&NHgithook::nhversioning;
&NHgithook::POST;
exit 0;
#STARTUP-END
&NHgithook::PRE;
+&NHgithook::nhversioning;
&NHgithook::POST;
exit 0;
chomp $gitdir;
push(@INC, $gitdir.$PDS."hooks");
}
+
use NHgithook;
#STARTUP-END
&NHgithook::PRE;
+&NHgithook::nhversioning;
&NHgithook::POST;
exit 0;
.I date.h
and
.I options
+file. It will read
+.I dat/gitinfo.txt
+,only if it is present, to obtain
+.B githash=
+and
+.B gitbranch=
+ info and include related preprocessor #defines in
+.I date.h
file.
.br
.TP
[
.I playernames
]
+[
+.B \-\-version
+]
.ad
.hy 14
.\" Make sure path is not hyphenated below
the list of top scorers, and a subdirectory
.I save
where games are saved.
+.PP
+.B \-\-version
+can be used to cause NetHack to show the version information it
+was compiled with, then exit. That will include the
+.I git
+commit hash if the information was available when the game was compiled.
+On some platforms, such as windows and macosx, a variation
+.B \-\-version:paste
+can be used to cause NetHack to show the version information, then exit,
+while also leaving a copy of the version information in the paste buffer
+or clipboard for potential insertion into things like bug reports.
.SH AUTHORS
.PP
Jay Fenlason (+ Kenny Woodland, Mike Thome and Jon Payne) wrote the
E const char *ARGV0;
#endif
+enum earlyarg {ARG_DEBUG, ARG_VERSION};
+
+struct early_opt {
+ enum earlyarg e;
+ const char *name;
+ int minlength;
+ boolean valallowed;
+};
+
#undef E
#endif /* DECL_H */
E void NDECL(newgame);
E void FDECL(welcome, (BOOLEAN_P));
E time_t NDECL(get_realtime);
+E boolean FDECL(argcheck, (int, char **, enum earlyarg));
/* ### apply.c ### */
E unsigned long FDECL(get_feature_notice_ver, (char *));
E unsigned long NDECL(get_current_feature_ver);
E const char *FDECL(copyright_banner_line, (int));
+E void FDECL(early_version_info, (BOOLEAN_P));
#ifdef RUNTIME_PORT_ID
E char *FDECL(get_port_id, (char *));
#endif
+#ifdef RUNTIME_PASTEBUF_SUPPORT
+E void FDECL(port_insert_pastebuf, (char *));
+#endif
/* ### video.c ### */
#define PORT_DEBUG /* include ability to debug international keyboard issues \
*/
+#define RUNTIME_PORT_ID /* trigger run-time port identification for \
+ * identification of exe CPU architecture \
+ */
+#define RUNTIME_PASTEBUF_SUPPORT
+
+
#define SAFERHANGUP /* Define SAFERHANGUP to delay hangup processing \
* until the main command loop. 'safer' because it \
* avoids certain cheats and also avoids losing \
#endif
#endif /* _MSC_VER */
-
-#define RUNTIME_PORT_ID /* trigger run-time port identification for \
- * identification of exe CPU architecture \
- */
-
/* The following is needed for prototypes of certain functions */
#if defined(_MSC_VER)
#include <process.h> /* Provides prototypes of exit(), spawn() */
#endif /* LINUX */
#endif /* GNOME_GRAPHICS */
+#ifdef __APPLE__
+# define RUNTIME_PASTEBUF_SUPPORT
+#endif
+
#endif /* UNIXCONF_H */
#endif /* UNIX */
}
}
+/*
+ * Argument processing helpers - for xxmain() to share
+ * and call.
+ *
+ * These should return TRUE if the argument matched,
+ * whether the processing of the argument was
+ * successful or not.
+ *
+ * Most of these do their thing, then after returning
+ * to xxmain(), the code exits without starting a game.
+ *
+ */
+
+static struct early_opt earlyopts[] = {
+ {ARG_DEBUG, "debug", 5, FALSE},
+ {ARG_VERSION, "version", 4, TRUE},
+};
+
+boolean
+argcheck(argc, argv, e_arg)
+int argc;
+char *argv[];
+enum earlyarg e_arg;
+{
+ int i, idx;
+ boolean match = FALSE;
+ char *userea = (char *)0, *dashdash = "";
+
+ for (idx = 0; idx < SIZE(earlyopts); idx++) {
+ if (earlyopts[idx].e == e_arg)
+ break;
+ }
+ if ((idx >= SIZE(earlyopts)) || (argc <= 1))
+ return FALSE;
+
+ for (i = 1; i < argc; ++i) {
+ if (argv[i][0] != '-')
+ continue;
+ if (argv[i][1] == '-') {
+ userea = &argv[i][2];
+ dashdash = "-";
+ } else {
+ userea = &argv[i][1];
+ }
+ match = match_optname(userea, earlyopts[idx].name,
+ earlyopts[idx].minlength, earlyopts[idx].valallowed);
+ if (match) break;
+ }
+
+ if (match) {
+ switch(e_arg) {
+ case ARG_DEBUG:
+ break;
+ case ARG_VERSION: {
+ boolean insert_into_pastebuf = FALSE;
+ const char *extended_opt = index(userea,':');
+
+ if (!extended_opt)
+ extended_opt = index(userea, '=');
+
+ if (extended_opt) {
+ extended_opt++;
+ if (match_optname(extended_opt, "paste",
+ 5, FALSE)) {
+ insert_into_pastebuf = TRUE;
+ } else {
+ raw_printf(
+ "-%sversion can only be extended with -%sversion:paste.\n",
+ dashdash, dashdash);
+ return TRUE;
+ }
+ }
+ early_version_info(insert_into_pastebuf);
+ return TRUE;
+ break;
+ }
+ default:
+ break;
+ }
+ };
+ return FALSE;
+}
/*allmain.c*/
-/* NetHack 3.6 version.c $NHDT-Date: 1506993902 2017/10/03 01:25:02 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.44 $ */
+/* NetHack 3.6 version.c $NHDT-Date: 1517140532 2018/01/28 11:55:32 $ $NHDT-Branch: nhmall-githash3 $:$NHDT-Revision: 1.50 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
#include "patchlevel.h"
#endif
+#if defined(NETHACK_GIT_SHA)
+const char * NetHack_git_sha = NETHACK_GIT_SHA;
+#endif
+#if defined(NETHACK_GIT_BRANCH)
+const char * NetHack_git_branch = NETHACK_GIT_BRANCH;
+#endif
+
STATIC_DCL void FDECL(insert_rtoption, (char *));
/* fill buffer with short version (so caller can avoid including date.h) */
getversionstring(buf)
char *buf;
{
- int details = 0;
+ boolean details = FALSE;
Strcpy(buf, VERSION_ID);
-#if defined(RUNTIME_PORT_ID)
- details++;
+#if defined(RUNTIME_PORT_ID) || \
+ defined(NETHACK_GIT_SHA) || defined(NETHACK_GIT_BRANCH)
+ details = TRUE;
#endif
if (details) {
int c = 0;
+#if defined(RUNTIME_PORT_ID)
char tmpbuf[BUFSZ];
char *tmp = (char *)0;
+#endif
Sprintf(eos(buf), " (");
#if defined(RUNTIME_PORT_ID)
tmp = get_port_id(tmpbuf);
if (tmp)
Sprintf(eos(buf), "%s%s", c++ ? "," : "", tmp);
+#endif
+#if defined(NETHACK_GIT_SHA)
+ if (NetHack_git_sha)
+ Sprintf(eos(buf), "%s%s", c++ ? "," : "", NetHack_git_sha);
+#endif
+#if defined(NETHACK_GIT_BRANCH)
+ if (NetHack_git_branch)
+ Sprintf(eos(buf), "%s%s", c++ ? "," : "", NetHack_git_branch);
#endif
Sprintf(eos(buf), ")");
}
return 0;
}
+void early_version_info(pastebuf)
+boolean pastebuf;
+{
+ char buf[BUFSZ], buf2[BUFSZ];
+ char *tmp = getversionstring(buf);
+
+ /* this is early enough that we have to do
+ our own line-splitting */
+ if (tmp) {
+ tmp = strstri(buf," (");
+ if (tmp) *tmp++ = '\0';
+ }
+
+ Sprintf(buf2, "%s\n", buf);
+ if (tmp) Sprintf(eos(buf2), "%s\n", tmp);
+ raw_printf("%s", buf2);
+
+ if (pastebuf) {
+#ifdef RUNTIME_PASTEBUF_SUPPORT
+ /*
+ * Call a platform/port-specific routine to insert the
+ * version information into a paste buffer. Useful for
+ * easy inclusion in bug reports.
+ */
+ port_insert_pastebuf(buf2);
+#else
+ raw_printf("%s", "Paste buffer copy is not available.\n");
+#endif
+ }
+}
+
extern const char regex_id[];
/*
Strcpy(hackdir, HACKDIR);
#endif
if (argc > 1) {
+ if (argcheck(argc, argv, ARG_VERSION))
+ nethack_exit(EXIT_SUCCESS);
+
if (!strncmp(argv[1], "-d", 2) && argv[1][2] != 'e') {
/* avoid matching "-dec" for DECgraphics; since the man page
* says -d directory, hope nobody's using -desomething_else
dir = nh_getenv("HACKDIR");
if (argc > 1) {
+ if (argcheck(argc, argv, ARG_VERSION))
+ exit(EXIT_SUCCESS);
+
if (!strncmp(argv[1], "-d", 2) && argv[1][2] != 'e') {
/* avoid matching "-dec" for DECgraphics; since the man page
* says -d directory, hope nobody's using -desomething_else
return buf;
}
+#ifdef __APPLE__
+extern int errno;
+
+void
+port_insert_pastebuf(buf)
+char *buf;
+{
+ /* This should be replaced when there is a Cocoa port. */
+ const char *errfmt;
+ size_t len;
+ FILE *PB = popen("/usr/bin/pbcopy","w");
+ if(!PB){
+ errfmt = "Unable to start pbcopy (%d)\n";
+ goto error;
+ }
+
+ len = strlen(buf);
+ /* Remove the trailing \n, carefully. */
+ if(buf[len-1] == '\n') len--;
+
+ /* XXX Sorry, I'm too lazy to write a loop for output this short. */
+ if(len!=fwrite(buf,1,len,PB)){
+ errfmt = "Error sending data to pbcopy (%d)\n";
+ goto error;
+ }
+
+ if(pclose(PB)!=-1){
+ return;
+ }
+ errfmt = "Error finishing pbcopy (%d)\n";
+
+error:
+ raw_printf(errfmt,strerror(errno));
+}
+#endif
+
/*unixmain.c*/
/* globals required within here */
HANDLE ffhandle = (HANDLE) 0;
WIN32_FIND_DATA ffd;
+typedef HWND(WINAPI *GETCONSOLEWINDOW)();
+static HWND GetConsoleHandle(void);
+static HWND GetConsoleHwnd(void);
/* The function pointer nt_kbhit contains a kbhit() equivalent
* which varies depending on which window port is active.
msmsg(interjection_buf[interjection_type]);
}
+#ifdef RUNTIME_PASTEBUF_SUPPORT
+
+void port_insert_pastebuf(buf)
+char *buf;
+{
+ /* This implementation will utilize the windows clipboard
+ * to accomplish this.
+ */
+
+ char *tmp = buf;
+ HWND hwndConsole = GetConsoleHandle();
+ HGLOBAL hglbCopy;
+ WCHAR *w, w2[2];
+ int cc, rc, abytes;
+ LPWSTR lpwstrCopy;
+ HANDLE hresult;
+
+ if (!buf || (hwndConsole == NULL))
+ return;
+
+ cc = strlen(buf);
+ /* last arg=0 means "tell me the size of the buffer that I need" */
+ rc = MultiByteToWideChar(GetConsoleOutputCP(), 0, buf, -1, w2, 0);
+ if (!rc) return;
+
+ abytes = rc * sizeof(WCHAR);
+ w = (WCHAR *)alloc(abytes);
+ /* Housekeeping need: +free(w) */
+
+ rc = MultiByteToWideChar(GetConsoleOutputCP(), 0, buf, -1, w, rc);
+ if (!rc) {
+ free(w);
+ return;
+ }
+ if (!OpenClipboard(hwndConsole)) {
+ free(w);
+ return;
+ }
+ /* Housekeeping need: +CloseClipboard(), free(w) */
+
+ EmptyClipboard();
+
+ /* allocate global mem obj to hold the text */
+
+ hglbCopy = GlobalAlloc(GMEM_MOVEABLE, abytes);
+ if (hglbCopy == NULL) {
+ CloseClipboard();
+ free(w);
+ return;
+ }
+ /* Housekeeping need: +GlobalFree(hglbCopy), CloseClipboard(), free(w) */
+
+ lpwstrCopy = (LPWSTR)GlobalLock(hglbCopy);
+ /* Housekeeping need: +GlobalUnlock(hglbCopy), GlobalFree(hglbCopy),
+ CloseClipboard(), free(w) */
+
+ memcpy(lpwstrCopy, w, abytes);
+ GlobalUnlock(hglbCopy);
+ /* Housekeeping need: GlobalFree(hglbCopy), CloseClipboard(), free(w) */
+
+ /* put it on the clipboard */
+ hresult = SetClipboardData(CF_UNICODETEXT, hglbCopy);
+ if (!hresult) {
+ raw_printf("Error copying to clipboard.\n");
+ GlobalFree(hglbCopy); /* only needed if clipboard didn't accept data */
+ }
+ /* Housekeeping need: CloseClipboard(), free(w) */
+
+ CloseClipboard();
+ free(w);
+ return;
+}
+
+static HWND
+GetConsoleHandle(void)
+{
+ HMODULE hMod = GetModuleHandle("kernel32.dll");
+ GETCONSOLEWINDOW pfnGetConsoleWindow =
+ (GETCONSOLEWINDOW) GetProcAddress(hMod, "GetConsoleWindow");
+ if (pfnGetConsoleWindow)
+ return pfnGetConsoleWindow();
+ else
+ return GetConsoleHwnd();
+}
+
+static HWND
+GetConsoleHwnd(void)
+{
+ int iterations = 0;
+ HWND hwndFound = 0;
+ char OldTitle[1024], NewTitle[1024], TestTitle[1024];
+
+ /* Get current window title */
+ GetConsoleTitle(OldTitle, sizeof OldTitle);
+
+ (void) sprintf(NewTitle, "NETHACK%d/%d", GetTickCount(),
+ GetCurrentProcessId());
+ SetConsoleTitle(NewTitle);
+
+ GetConsoleTitle(TestTitle, sizeof TestTitle);
+ while (strcmp(TestTitle, NewTitle) != 0) {
+ iterations++;
+ /* sleep(0); */
+ GetConsoleTitle(TestTitle, sizeof TestTitle);
+ }
+ hwndFound = FindWindow(NULL, NewTitle);
+ SetConsoleTitle(OldTitle);
+ /* printf("%d iterations\n", iterations); */
+ return hwndFound;
+}
+
+#endif
+
#ifdef RUNTIME_PORT_ID
/*
* _M_IX86 is Defined for x86 processors. This is not defined for x64
#define QTXT_O_FILE "quest.dat"
#define VIS_TAB_H "vis_tab.h"
#define VIS_TAB_C "vis_tab.c"
+#define GITINFO_FILE "gitinfo.txt"
/* locations for those files */
#ifdef AMIGA
#define FILE_PREFIX
static char *FDECL(xcrypt, (const char *));
static unsigned long FDECL(read_rumors_file,
(const char *, int *, long *, unsigned long));
+static boolean FDECL(get_gitinfo, (char *, char *));
static void FDECL(do_rnd_access_file, (const char *));
static boolean FDECL(d_filter, (char *));
static boolean FDECL(h_filter, (char *));
static char *FDECL(tmpdup, (const char *));
static char *FDECL(limit, (char *, int));
static char *FDECL(eos, (char *));
+static int FDECL(case_insensitive_comp, (const char *, const char *));
/* input, output, tmp */
static FILE *ifp, *ofp, *tfp;
#else
time_t clocktim = 0;
#endif
+ char githash[BUFSZ], gitbranch[BUFSZ];
char *c, cbuf[60], buf[BUFSZ];
const char *ul_sfx;
Fprintf(ofp, "#define COPYRIGHT_BANNER_C \\\n \"%s\"\n",
bannerc_string(buf, cbuf));
Fprintf(ofp, "\n");
+ if (get_gitinfo(githash, gitbranch)) {
+ Fprintf(ofp, "#define NETHACK_GIT_SHA \"%s\"\n", githash);
+ Fprintf(ofp, "#define NETHACK_GIT_BRANCH \"%s\"\n", gitbranch);
+ }
#ifdef AMIGA
{
struct tm *tm = localtime((time_t *) &clocktim);
return;
}
+boolean
+get_gitinfo(githash, gitbranch)
+char *githash, *gitbranch;
+{
+ FILE *gifp;
+ size_t len;
+ char infile[600];
+ char *line, *strval, *opt, *c, *end;
+ boolean havebranch = FALSE, havehash = FALSE;
+
+ if (!githash || !gitbranch) return FALSE;
+
+ Sprintf(infile, DATA_IN_TEMPLATE, GITINFO_FILE);
+ if (!(gifp = fopen(infile, RDTMODE))) {
+ /* perror(infile); */
+ return FALSE;
+ }
+
+ /* read the gitinfo file */
+ while ((line = fgetline(gifp)) != 0) {
+ strval = index(line, '=');
+ if (strval && strlen(strval) < (BUFSZ-1)) {
+ opt = line;
+ *strval++ = '\0';
+ /* strip off the '\n' */
+ if ((c = index(strval, '\n')) != 0)
+ *c = '\0';
+ if ((c = index(opt, '\n')) != 0)
+ *c = '\0';
+ /* strip leading and trailing white space */
+ while (*strval == ' ' || *strval == '\t')
+ strval++;
+ end = eos(strval);
+ while (--end >= strval && (*end == ' ' || *end == '\t'))
+ *end = '\0';
+ while (*opt == ' ' || *opt == '\t')
+ opt++;
+ end = eos(opt);
+ while (--end >= opt && (*end == ' ' || *end == '\t'))
+ *end = '\0';
+
+ len = strlen(opt);
+ if ((len >= strlen("gitbranch")) && !case_insensitive_comp(opt, "gitbranch")) {
+ Strcpy(gitbranch, strval);
+ havebranch = TRUE;
+ }
+ if ((len >= strlen("githash")) && !case_insensitive_comp(opt, "githash")) {
+ Strcpy(githash, strval);
+ havehash = TRUE;
+ }
+ }
+ }
+ Fclose(gifp);
+ if (havebranch && havehash)
+ return TRUE;
+ return FALSE;
+}
+
+static int
+case_insensitive_comp(s1, s2)
+const char *s1;
+const char *s2;
+{
+ uchar u1, u2;
+
+ for (;; s1++, s2++) {
+ u1 = (uchar) *s1;
+ if (isupper(u1))
+ u1 = tolower(u1);
+ u2 = (uchar) *s2;
+ if (isupper(u2))
+ u2 = tolower(u2);
+ if (u1 == '\0' || u1 != u2)
+ break;
+ }
+ return u1 - u2;
+}
+
static char save_bones_compat_buf[BUFSZ];
static void