diff --git a/exec/hgmain/Cargo.toml b/exec/hgmain/Cargo.toml index 3b4aa7eb17..ddffa5552f 100644 --- a/exec/hgmain/Cargo.toml +++ b/exec/hgmain/Cargo.toml @@ -14,6 +14,7 @@ path = "../../lib/encoding" path = "../../lib/hgpython" [features] +buildinfo = [] default = [] with_chg = [] hgdev = ["hgpython/hgdev"] diff --git a/exec/hgmain/build.rs b/exec/hgmain/build.rs index 4ba09c21b8..2ea3566f4c 100644 --- a/exec/hgmain/build.rs +++ b/exec/hgmain/build.rs @@ -14,6 +14,11 @@ fn main() { "cargo:rerun-if-changed={}", lib_dir.join("libchg.a").display() ); + #[cfg(feature = "buildinfo")] + println!( + "cargo:rerun-if-changed={}", + lib_dir.join("buildinfo.a").display() + ); } } } diff --git a/exec/hgmain/src/buildinfo.rs b/exec/hgmain/src/buildinfo.rs new file mode 100644 index 0000000000..3a7da4e2c8 --- /dev/null +++ b/exec/hgmain/src/buildinfo.rs @@ -0,0 +1,7 @@ +// Copyright Facebook, Inc. 2019 + +#[cfg(feature = "buildinfo")] +#[link(name = "buildinfo", kind = "static")] +extern "C" { + pub fn print_buildinfo(); +} diff --git a/exec/hgmain/src/main.rs b/exec/hgmain/src/main.rs index 2344509adc..813a4cba8a 100644 --- a/exec/hgmain/src/main.rs +++ b/exec/hgmain/src/main.rs @@ -1,4 +1,5 @@ // Copyright Facebook, Inc. 2018 + #[cfg(feature = "with_chg")] extern crate dirs; extern crate encoding; @@ -7,6 +8,7 @@ extern crate hgpython; extern crate libc; use hgpython::HgPython; +mod buildinfo; #[cfg(feature = "with_chg")] mod chg; #[cfg(feature = "with_chg")] @@ -23,6 +25,20 @@ fn call_embedded_python() { } fn main() { + #[cfg(feature = "buildinfo")] + { + // This code path keeps buildinfo-related symbols alive. + use std::env; + if let Some(arg0) = env::args().nth(0) { + if arg0.ends_with("buildinfo") { + unsafe { + buildinfo::print_buildinfo(); + } + return; + } + } + } + #[cfg(feature = "with_chg")] maybe_call_chg(); call_embedded_python(); diff --git a/setup.py b/setup.py index 6f2c92a515..a302ba7855 100644 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ import os import py_compile import re import shutil +import socket import stat import struct import subprocess @@ -452,15 +453,24 @@ def localhgenv(): return env -def pickversion(): - hg = findhg() +hg = findhg() + + +def hgtemplate(template, cast=None): if not hg: - # if hg is not found, fallback to a fixed version - return "4.4.2" + return None + result = sysstr(hg.run(["log", "-r.", "-T", template])) + if result and cast: + result = cast(result) + return result + + +def pickversion(): # New version system: YYMMDD_HHmmSS_hash # This is duplicated a bit from build_rpm.py:auto_release_str() template = '{sub("([:+-]|\d\d\d\d$)", "",date|isodatesec)} {node|short}' - out = sysstr(hg.run(["log", "-r.", "-T", template])) + # if hg is not found, fallback to a fixed version + out = hgtemplate(template) or "" # Some tools parse this number to figure out if they support this version of # Mercurial, so prepend with 4.4.2. # ex. 4.4.2_20180105_214829_58fda95a0202 @@ -502,6 +512,77 @@ if not os.path.isdir(builddir): ensureexists(builddir) +def writebuildinfoc(): + """Write build/buildinfo.c""" + commithash = hgtemplate("{node}") + commitunixtime = hgtemplate('{sub("[^0-9].*","",date)}', cast=int) + + # Search 'extractBuildInfoFromELF' in fbcode for supported fields. + buildinfo = { + "Host": socket.gethostname(), + "PackageName": os.environ.get("RPM_PACKAGE_NAME") + or os.environ.get("PACKAGE_NAME"), + "PackageRelease": os.environ.get("RPM_PACKAGE_RELEASE") + or os.environ.get("PACKAGE_RELEASE"), + "PackageVersion": os.environ.get("RPM_PACKAGE_VERSION") + or os.environ.get("PACKAGE_VERSION"), + "Path": os.getcwd(), + "Platform": os.environ.get("RPM_OS"), + "Revision": commithash, + "RevisionCommitTimeUnix": commitunixtime, + "TimeUnix": int(time.time()), + "UpstreamRevision": commithash, + "UpstreamRevisionCommitTimeUnix": commitunixtime, + "User": os.environ.get("USER"), + } + + buildinfosrc = """ +#include +#include +""" + for name, value in sorted(buildinfo.items()): + if isinstance(value, str): + buildinfosrc += 'const char *BuildInfo_k%s = "%s";\n' % ( + name, + value.replace('"', '\\"'), + ) + elif isinstance(value, int): + # The only usage of int is timestamp + buildinfosrc += "const time_t BuildInfo_k%s = %d;\n" % (name, value) + + buildinfosrc += """ +/* This function keeps references of the symbols and prevents them from being + * optimized out if this function is used. */ +void print_buildinfo() { +""" + for name, value in sorted(buildinfo.items()): + if isinstance(value, str): + buildinfosrc += ( + ' fprintf(stderr, "%(name)s: %%s (at %%p)\\n", BuildInfo_k%(name)s, BuildInfo_k%(name)s);\n' + % {"name": name} + ) + elif isinstance(value, int): + buildinfosrc += ( + ' fprintf(stderr, "%(name)s: %%lu (at %%p)\\n", (long unsigned)BuildInfo_k%(name)s, &BuildInfo_k%(name)s) ;\n' + % {"name": name} + ) + buildinfosrc += """ +} +""" + + path = pjoin(builddir, "buildinfo.c") + write_if_changed(path, buildinfosrc) + return path + + +# If NEED_BUILDINFO is set, write buildinfo. +# For rpmbuild, imply NEED_BUILDINFO. +needbuildinfo = bool(os.environ.get("NEED_BUILDINFO", "RPM_PACKAGE_NAME" in os.environ)) + +if needbuildinfo: + buildinfocpath = writebuildinfoc() + + try: oldpolicy = os.environ.get("HGMODULEPOLICY", None) os.environ["HGMODULEPOLICY"] = "py" @@ -1833,6 +1914,16 @@ libraries = [ }, ), ] +if needbuildinfo: + libraries += [ + ( + "buildinfo", + { + "sources": [buildinfocpath], + "extra_args": filter(None, cflags + [WALL, PIC]), + }, + ) + ] if not iswindows: libraries.append( @@ -2104,6 +2195,7 @@ hgmainfeatures = ( filter( None, [ + "buildinfo" if needbuildinfo else None, "hgdev" if os.environ.get("HGDEV") else None, "with_chg" if not iswindows else None, ],