From f2beb4443abeb75a77cbe09548d13fbab3c5c7b9 Mon Sep 17 00:00:00 2001 From: Lirusaito Date: Fri, 19 Apr 2019 16:52:40 -0400 Subject: [PATCH] This should fix Linux, thanks Drake. --- indra/cmake/BuildPackagesInfo.cmake | 2 + indra/cmake/CMakeLists.txt | 1 - indra/cmake/LLAddBuildTest.cmake | 11 - indra/cmake/LLSharedLibs.cmake | 56 +++-- indra/cmake/RunBuildTest.cmake | 20 -- indra/cmake/run_build_test.py | 340 ++++++++++++++++++++++++++++ 6 files changed, 374 insertions(+), 56 deletions(-) delete mode 100644 indra/cmake/RunBuildTest.cmake create mode 100644 indra/cmake/run_build_test.py diff --git a/indra/cmake/BuildPackagesInfo.cmake b/indra/cmake/BuildPackagesInfo.cmake index 6b2410ea5..64a353ba2 100644 --- a/indra/cmake/BuildPackagesInfo.cmake +++ b/indra/cmake/BuildPackagesInfo.cmake @@ -14,5 +14,7 @@ add_custom_command(OUTPUT packages-info.txt DEPENDS ${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py ${CMAKE_SOURCE_DIR}/../autobuild.xml COMMAND ${PYTHON_EXECUTABLE} + ${CMAKE_SOURCE_DIR}/cmake/run_build_test.py -DAUTOBUILD_ADDRSIZE=${ADDRESS_SIZE} + ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py "${VIEWER_CHANNEL}" "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}" > packages-info.txt ) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 215f7a300..932730441 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -85,7 +85,6 @@ set(cmake_SOURCE_FILES Prebuilt.cmake PulseAudio.cmake Python.cmake - RunBuildTest.cmake StateMachine.cmake TemplateCheck.cmake Tut.cmake diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 290db9f22..7875e5bfc 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -105,17 +105,6 @@ MACRO(ADD_BUILD_TEST_INTERNAL name parent libraries source_files) IF (NOT "$ENV{LD_LIBRARY_PATH}" STREQUAL "") SET(LD_LIBRARY_PATH "$ENV{LD_LIBRARY_PATH}:${LD_LIBRARY_PATH}") ENDIF (NOT "$ENV{LD_LIBRARY_PATH}" STREQUAL "") - ADD_CUSTOM_COMMAND( - OUTPUT ${TEST_OUTPUT} - COMMAND - ${CMAKE_COMMAND} - "-DLD_LIBRARY_PATH=${LD_LIBRARY_PATH}" - "-DTEST_CMD:STRING=${TEST_CMD}" - -P ${CMAKE_SOURCE_DIR}/cmake/RunBuildTest.cmake - DEPENDS ${name}_test - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - VERBATIM - ) ADD_CUSTOM_TARGET(${name}_test_ok ALL DEPENDS ${TEST_OUTPUT}) IF (${parent}) diff --git a/indra/cmake/LLSharedLibs.cmake b/indra/cmake/LLSharedLibs.cmake index 4ba1d4c02..f69b45cd9 100644 --- a/indra/cmake/LLSharedLibs.cmake +++ b/indra/cmake/LLSharedLibs.cmake @@ -1,32 +1,40 @@ # ll_deploy_sharedlibs_command # target_exe: the cmake target of the executable for which the shared libs will be deployed. macro(ll_deploy_sharedlibs_command target_exe) - set(OUTPUT_PATH $) + set(TARGET_LOCATION $) + get_filename_component(OUTPUT_PATH ${TARGET_LOCATION} PATH) - if(DARWIN) - SET_TEST_PATH(SEARCH_DIRS) - get_target_property(IS_BUNDLE ${target_exe} MACOSX_BUNDLE) - if(IS_BUNDLE) - set(OUTPUT_PATH ${OUTPUT_PATH}/../Resources) - endif(IS_BUNDLE) - elseif(WINDOWS) - SET_TEST_PATH(SEARCH_DIRS) - LIST(APPEND SEARCH_DIRS "$ENV{SystemRoot}/system32") - elseif(LINUX) - SET_TEST_PATH(SEARCH_DIRS) - set(OUTPUT_PATH ${OUTPUT_PATH}/lib) - endif(DARWIN) + # It's not clear that this does anything useful for us on Darwin. It has + # been broken for some time now; the BIN_NAME was being constructed as a + # ridiculous nonexistent path with duplicated segments. Fixing that only + # produces ominous spammy warnings: at the time the command below is run, we + # have not yet populated the nested mac-crash-logger.app/Contents/Resources + # with the .dylibs with which it was linked. Moreover, the form of the + # embedded @executable_path/../Resources/mumble.dylib pathname confuses the + # GetPrerequisites.cmake tool invoked by DeploySharedLibs.cmake. It seems + # clear that we have long since accomplished by other means what this was + # originally supposed to do. Skipping it only eliminates an annoying + # non-fatal error. + if(NOT DARWIN) + if(WINDOWS) + SET_TEST_PATH(SEARCH_DIRS) + LIST(APPEND SEARCH_DIRS "$ENV{SystemRoot}/system32") + elseif(LINUX) + SET_TEST_PATH(SEARCH_DIRS) + set(OUTPUT_PATH ${OUTPUT_PATH}/lib) + endif(WINDOWS) - add_custom_command( - TARGET ${target_exe} POST_BUILD - COMMAND ${CMAKE_COMMAND} - ARGS - "-DBIN_NAME=\"$\"" - "-DSEARCH_DIRS=\"${SEARCH_DIRS}\"" - "-DDST_PATH=\"${OUTPUT_PATH}\"" - "-P" - "${CMAKE_SOURCE_DIR}/cmake/DeploySharedLibs.cmake" - ) + add_custom_command( + TARGET ${target_exe} POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + "-DBIN_NAME=\"${TARGET_LOCATION}\"" + "-DSEARCH_DIRS=\"${SEARCH_DIRS}\"" + "-DDST_PATH=\"${OUTPUT_PATH}\"" + "-P" + "${CMAKE_SOURCE_DIR}/cmake/DeploySharedLibs.cmake" + ) + endif(NOT DARWIN) endmacro(ll_deploy_sharedlibs_command) diff --git a/indra/cmake/RunBuildTest.cmake b/indra/cmake/RunBuildTest.cmake deleted file mode 100644 index dc6c942b7..000000000 --- a/indra/cmake/RunBuildTest.cmake +++ /dev/null @@ -1,20 +0,0 @@ -#This cmake script is meant to be run as a build time custom command. -#The script is run using cmake w/ the -P option. -# parameters are passed to this scripts execution with the -D option. -# A full command line would look like this: -# cmake -D LD_LIBRARY_PATH=~/checkout/libraries -D TEST_CMD=./llunit_test -D ARGS=--touch=llunit_test_ok.txt -P RunBuildTest.cmake - -# Parameters: -# LD_LIBRARY_PATH: string, What to set the LD_LIBRARY_PATH env var. -# TEST_CMD: string list, command to run the unit test with, followed by its args. -set(ENV{LD_LIBRARY_PATH} ${LD_LIBRARY_PATH}) -#message("Running: ${TEST_CMD}") -execute_process( - COMMAND ${TEST_CMD} - RESULT_VARIABLE RES - ) - -if(NOT ${RES} STREQUAL 0) - message(STATUS "Failure running: ${TEST_CMD}") - message(FATAL_ERROR "Error: ${RES}") -endif(NOT ${RES} STREQUAL 0) \ No newline at end of file diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py new file mode 100644 index 000000000..210e43b23 --- /dev/null +++ b/indra/cmake/run_build_test.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python +"""\ +@file run_build_test.py +@author Nat Goodspeed +@date 2009-09-03 +@brief Helper script to allow CMake to run some command after setting + environment variables. + +CMake has commands to run an external program. But remember that each CMake +command must be backed by multiple build-system implementations. Unfortunately +it seems CMake can't promise that every target build system can set specified +environment variables before running the external program of interest. + +This helper script is a workaround. It simply sets the requested environment +variables and then executes the program specified on the rest of its command +line. + +Example: + +python run_build_test.py -DFOO=bar myprog somearg otherarg + +sets environment variable FOO=bar, then runs: +myprog somearg otherarg + +$LicenseInfo:firstyear=2009&license=viewerlgpl$ +Second Life Viewer Source Code +Copyright (C) 2009-2010, Linden Research, Inc. + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; +version 2.1 of the License only. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +$/LicenseInfo$ +""" + +import os +import sys +import errno +import HTMLParser +import re +import signal +import subprocess +import logging + +def main(command, arguments=[], libpath=[], vars={}): + """Pass: + command is the command to be executed + + argument is a sequence (e.g. a list) of strings to be passed to command + + libpath is a sequence of directory pathnames. These will be appended to + the platform-specific dynamic library search path environment variable. + + vars is a dict of arbitrary (var, value) pairs to be added to the + environment before running 'command'. + + This function runs the specified command, waits for it to terminate and + returns its return code. This will be negative if the command terminated + with a signal, else it will be the process's specified exit code. + """ + # Handle platform-dependent libpath first. + if sys.platform == "win32": + lpvars = ["PATH"] + elif sys.platform == "darwin": + lpvars = ["LD_LIBRARY_PATH", "DYLD_LIBRARY_PATH"] + elif sys.platform.startswith("linux"): + lpvars = ["LD_LIBRARY_PATH"] + else: + # No idea what the right pathname might be! But only crump if this + # feature is requested. + if libpath: + raise RuntimeError("run_build_test: unknown platform %s" % sys.platform) + lpvars = [] + for var in lpvars: + # Split the existing path. Bear in mind that the variable in question + # might not exist; instead of KeyError, just use an empty string. + dirs = os.environ.get(var, "").split(os.pathsep) + # Append the sequence in libpath + log.info("%s += %r" % (var, libpath)) + for dir in libpath: + # append system paths at the end + if dir in ('/lib', '/usr/lib'): + dirs.append(dir) + # prepend non-system paths + else: + dirs.insert(0, dir) + + # Filter out some useless pieces + clean_dirs = [] + for dir in dirs: + if dir and dir not in ('', '.'): + clean_dirs.append(dir) + + # Now rebuild the path string. This way we use a minimum of separators + # -- and we avoid adding a pointless separator when libpath is empty. + os.environ[var] = os.pathsep.join(clean_dirs) + log.info("%s = %r" % (var, os.environ[var])) + # Now handle arbitrary environment variables. The tricky part is ensuring + # that all the keys and values we try to pass are actually strings. + if vars: + log.info("Setting: %s" % ("\n".join(["%s=%s" % (key, value) for key, value in vars.iteritems()]))) + os.environ.update(dict([(str(key), str(value)) for key, value in vars.iteritems()])) + # Run the child process. + command_list = [command] + command_list.extend(arguments) + log.info("Running: %s" % " ".join(command_list)) + # Make sure we see all relevant output *before* child-process output. + sys.stdout.flush() + try: + return subprocess.call(command_list) + except OSError as err: + # If the caller is trying to execute a test program that doesn't + # exist, we want to produce a reasonable error message rather than a + # traceback. This happens when the build is halted by errors, but + # CMake tries to proceed with testing anyway . However, do + # NOT attempt to handle any error but "doesn't exist." + if err.errno != errno.ENOENT: + raise + # In practice, the pathnames into CMake's build tree are so long as to + # obscure the name of the test program. Just log its basename. + log.warn("No such program %s; check for preceding build errors" % \ + os.path.basename(command[0])) + # What rc should we simulate for missing executable? Windows produces + # 9009. + return 9009 + +# swiped from vita, sigh, seems like a Bad Idea to introduce dependency +def translate_rc(rc): + """ + Accept an rc encoded as for subprocess.Popen.returncode: + None means still running + int >= 0 means terminated voluntarily with specified rc + int < 0 means terminated by signal (-rc) + + Return a string explaining the outcome. In case of a signal, try to + name the corresponding symbol from the 'signal' module. + """ + if rc is None: + return "still running" + + if rc >= 0: + return "terminated with rc %s" % rc + + if sys.platform.startswith("win"): + # From http://stackoverflow.com/questions/20629027/process-finished-with-exit-code-1073741571 + # [-1073741571] is the signed integer representation of Microsoft's + # "stack overflow/stack exhaustion" error code 0xC00000FD. + # Anytime you see strange, large negative exit codes in windows, convert + # them to hex and then look them up in the ntstatus error codes + # http://msdn.microsoft.com/en-us/library/cc704588.aspx + + # Python bends over backwards to give you all the integer precision + # you need, avoiding truncation. But only with 32-bit signed ints is + # -1073741571 equivalent to 0xC00000FD! Explicitly truncate before + # converting. + hexrc = "0x%X" % (rc & 0xFFFFFFFF) + # At this point, we're only trying to format the rc to make it easier + # for a human being to understand. Any exception here -- file doesn't + # exist, HTML parsing error, unrecognized table structure, unknown key + # -- should NOT kill the script! It should only cause us to shrug and + # present our caller with the best information available. + try: + table = get_windows_table() + symbol, desc = table[hexrc] + except Exception, err: + log.error("(%s -- carrying on)" % err) + log.error("terminated with rc %s (%s)" % (rc, hexrc)) + else: + log.info("terminated with rc %s: %s: %s" % (hexrc, symbol, desc)) + + else: + # On Posix, negative rc means the child was terminated by signal -rc. + rc = -rc + for attr in dir(signal): + if attr.startswith('SIG') and getattr(signal, attr) == rc: + strc = attr + break + else: + strc = str(rc) + return "terminated by signal %s" % strc + +class TableParser(HTMLParser.HTMLParser): + """ + This HTMLParser subclass is designed to parse the table we know exists + in windows-rcs.html, hopefully without building in too much knowledge of + the specific way that table is currently formatted. + """ + # regular expression matching any string containing only whitespace + whitespace = re.compile(r'\s*$') + + def __init__(self): + # Because Python 2.x's HTMLParser is an old-style class, we must use + # old-style syntax to forward the __init__() call -- not super(). + HTMLParser.HTMLParser.__init__(self) + # this will collect all the data, eventually + self.table = [] + # Stack whose top (last item) indicates where to append current + # element data. When empty, don't collect data at all. + self.dest = [] + + def handle_starttag(self, tag, attrs): + if tag == "table": + # This is the outermost tag we recognize. Collect nested elements + # within self.table. + self.dest.append(self.table) + elif tag in ("tr", "td"): + # Nested elements whose contents we want to capture as sublists. + # To the list currently designated by the top of the dest stack, + # append a new empty sublist. + self.dest[-1].append([]) + # Now push THAT new, empty list as the new top of the dest stack. + self.dest.append(self.dest[-1][-1]) + elif tag == "p": + # We could handle

...

just like or , but that + # introduces an unnecessary extra level of nesting. Just skip. + pass + else: + # For any tag we don't recognize (notably ), push a new, empty + # list to the top of the dest stack. This new list is NOT + # referenced by anything in self.table; thus, when we pop it, any + # data we've collected inside that list will be discarded. + self.dest.append([]) + + def handle_endtag(self, tag): + # Because we avoid pushing self.dest for

in handle_starttag(), we + # must refrain from popping it for

here. + if tag != "p": + # For everything else, including unrecognized tags, pop the dest + # stack, reverting to outer collection. + self.dest.pop() + + def handle_startendtag(self, tag, attrs): + # The table of interest contains entries of the form: + #

0x00000000
STATUS_SUCCESS

+ # The
is very useful -- we definitely want two different data + # items for "0x00000000" and "STATUS_SUCCESS" -- but we don't need or + # want it to push, then discard, an empty list as it would if we let + # the default HTMLParser.handle_startendtag() call handle_starttag() + # followed by handle_endtag(). Just ignore
or any other + # singleton tag. + pass + + def handle_data(self, data): + # Outside the of interest, self.dest is empty. Do not bother + # collecting data when self.dest is empty. + # HTMLParser calls handle_data() with every chunk of whitespace + # between tags. That would be lovely if our eventual goal was to + # reconstitute the original input stream with its existing formatting, + # but for us, whitespace only clutters the table. Ignore it. + if self.dest and not self.whitespace.match(data): + # Here we're within our
and we have non-whitespace data. + # Append it to the list designated by the top of the dest stack. + self.dest[-1].append(data) + +# cache for get_windows_table() +_windows_table = None + +def get_windows_table(): + global _windows_table + # If we already loaded _windows_table, no need to load it all over again. + if _windows_table: + return _windows_table + + # windows-rcs.html was fetched on 2015-03-24 with the following command: + # curl -o windows-rcs.html \ + # https://msdn.microsoft.com/en-us/library/cc704588.aspx + parser = TableParser() + with open(os.path.join(os.path.dirname(__file__), "windows-rcs.html")) as hf: + # We tried feeding the file data to TableParser in chunks, to avoid + # buffering the entire file as a single string. Unfortunately its + # handle_data() cannot tell the difference between distinct calls + # separated by HTML tags, and distinct calls necessitated by a chunk + # boundary. Sigh! Read in the whole file. At the time this was + # written, it was only 500KB anyway. + parser.feed(hf.read()) + parser.close() + table = parser.table + + # With our parser, any row leaves a table entry + # consisting only of an empty list. Remove any such. + while table and not table[0]: + table.pop(0) + + # We expect rows of the form: + # [['0x00000000', 'STATUS_SUCCESS'], + # ['The operation completed successfully.']] + # The latter list will have multiple entries if Microsoft embedded
+ # or

...

in the text, in which case joining with '\n' is + # appropriate. + # Turn that into a dict whose key is the hex string, and whose value is + # the pair (symbol, desc). + _windows_table = dict((key, (symbol, '\n'.join(desc))) + for (key, symbol), desc in table) + + return _windows_table + +log=logging.getLogger(__name__) +logging.basicConfig() + +if __name__ == "__main__": + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("-d", "--debug", dest="loglevel", action="store_const", + const=logging.DEBUG, default=logging.INFO) + parser.add_argument("-D", "--define", dest="vars", default=[], action="append", + metavar="VAR=value", + help="Add VAR=value to the env variables defined") + parser.add_argument("-l", "--libpath", dest="libpath", default=[], action="append", + metavar="DIR", + help="Add DIR to the platform-dependent DLL search path") + parser.add_argument("command") + parser.add_argument('args', nargs=argparse.REMAINDER) + args = parser.parse_args() + + log.setLevel(args.loglevel) + + # What we have in opts.vars is a list of strings of the form "VAR=value" + # or possibly just "VAR". What we want is a dict. We can build that dict by + # constructing a list of ["VAR", "value"] pairs -- so split each + # "VAR=value" string on the '=' sign (but only once, in case we have + # "VAR=some=user=string"). To handle the case of just "VAR", append "" to + # the list returned by split(), then slice off anything after the pair we + # want. + rc = main(command=args.command, arguments=args.args, libpath=args.libpath, + vars=dict([(pair.split('=', 1) + [""])[:2] for pair in args.vars])) + if rc not in (None, 0): + log.error("Failure running: %s" % " ".join([args.command] + args.args)) + log.error("Error %s: %s" % (rc, translate_rc(rc))) + sys.exit((rc < 0) and 255 or rc)
...