#!/usr/bin/env python3 """ --------------------------------------------------------------------- file information --------------------------------------------------------------------- Name: This program is based on mtcompile-program.pl Purpose: Linux Minetest build script License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0. Attribution: OldCoder (Robert Kiraly) and Poikilos (Jake Gustafson) Revision: See program parameters section --------------------------------------------------------------------- important note --------------------------------------------------------------------- This software is provided on an AS IS basis with ABSOLUTELY NO WAR- RANTY. The entire risk as to the quality and performance of the software is with you. Should the software prove defective, you as- sume the cost of all necessary servicing, repair or correction. In no event will any of the developers, or any other party, be liable to anyone for damages arising out of use of the software, or inabil- ity to use the software. --------------------------------------------------------------------- overview --------------------------------------------------------------------- """ import sys import subprocess import shutil import re import tarfile import stat from zipfile import ZipFile import zipfile import platform import os def echo0(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) def customExit(msg): echo0(msg) sys.exit(1) def isExecutableFile(path): return os.path.isfile(path) and os.access(path, os.X_OK) def which(prog): """ Check for the given file within os.environ["PATH"], which contains paths separated by os.pathsep. NOTE: sys.path is NOT applicable, since it only has Python paths. """ for thisPath in os.environ["PATH"].split(os.pathsep): sub_path = os.path.join(thisPath, prog) if os.path.isfile(sub_path): print("* using {} as {}".format(sub_path, prog)) return sub_path return "" def zipdir(path, ziph): """ Zip an entire directory. See Mark Byers' Dec 6, 2009 answer edited by JosephH Feb 24, 2016 on """ # ziph is zipfile handle for root, dirs, files in os.walk(path): for file in files: ziph.write(os.path.join(root, file)) def endsWithAny(haystack, needles): for needle in needles: if haystack.endswith(needle): return True return False def containsAny(haystack, needles): for needle in needles: if needle in haystack: return True return False def startsWithAny(haystack, needles): for needle in needles: if haystack.startswith(needle): return True return False # Label must be single-quoted here USAGE_TEXT = """ Usage: {PROGNAME} --options --build The "--build" switch is required. It needs to be specified on the com- mand line or you'll get this usage text. The other switches are opt- ional. The command-line argument "build", specified without dashes, will also work. ---------------------------------------------------------------------- Background information related to "builds": 1. Minetest, or this version, uses a combined source and production tree. I.e., a single tree can serve both purposes until it's cleaned up for distribution. 2. The production tree is portable in the sense that it can be moved to different directories on a given system and the program will still work. 3. The production tree isn't portable in the sense that it'll run on different systems unless the "--portable" or "--makeprod" option swi- tch os used. 4. By default, this script deletes the source tree on each run, un- packs an included source tarball, and deletes unneeded "build" files after a build is completed. The last step results in a pure production as opposed to source tree. Command-line option switches can be used to modify this behavior. Examples include "--noclean" and "--gitreset". ---------------------------------------------------------------------- Option switches: --noclean # If --noclean is specified, this script tries to re- use the existing source tree, if there is one, and doesn't deleted "build" files afterward. The "--git*" and "--debug" switches imply this swi- tch. Aliases: --notidy --server # Build server & not client unless --client also --client # Build client & not server unless --server also Default: If neither is set, both are implied If just one is set, the other is off --postgresql # Enable PostgreSQL (requires installed copy) Aliases: --postgres --redis # Enable Redis (requires installed copy) --debug # Build a "debug" version of the program suitable for use with "gdb". --makeprod # Build a portable production release (ZIP file) of Linux Minetest. This is only needed by people who wish to redistribute the program. The switch implies --portable. It isn't compatible with --noclean or --debug. --portable # Build a portable version. If this isn't specified, the copy of Minetest built is tailored to your ma- chine and may only run on an identical machine (same hardware, distro, and distro release). At the same time, non-portable versions may be slightly faster. --gitreset # Delete any existing source tree and try to do a fresh "git clone". --gitpull # Try to update the current source tree using "git pull". If there is no source tree or it's not a "git" tree, this switch is the same as "--gitreset". The "git" switches require both the "git" software package and Internet access. --safe # Don't delete existing source trees automatically. --edgy # Build EdgyTest instead of Final Minetest. Implies "--fakemt4". --fakemt4 # Pretend to be MT 4. Implies "--oldproto". --oldproto # Limit network protocol used to level 32. For the mo- ment, this is the default mode and there is no way to disable it. --help # Display usage text and exit. Aliases: --usage For full documentation, see "linux-minetest-kit.txt". --MT_SRC= # Set the minetest source path to a # locally-modified copy, such as # $HOME/git/minetest Before using this script, please see doc/mtcompile-program-local.md in EnlivenMinetest for changes and progress on implementing features from mtcompile-program.pl. """ # ---------------------------------------------------------------------- # module setup # ---------------------------------------------------------------------- # TODO: Trap warnings to mimic the Perl version? #SIG["__WARN__"] = sub { die @_; } # ---------------------------------------------------------------------- # program parameters # ---------------------------------------------------------------------- def streamEdit(inPath, replacements): """ Replace parts similarly to sed ("stream editor"). See David Miller's Dec 13, 2010 answer edited by dpb Jul 23, 2013 on Sequential arguments: inPath -- Edit then save the file at this path. replacements -- a replacements dict where the key is the string or regex, and the value is the new string to use in cases of] matches. """ with open(inPath, "r") as sources: lines = sources.readlines() with open(inPath, "w") as sources: for rawLine in lines: line = rawLine for k, v in replacements.items(): line = re.sub(k, v, line) sources.write(line) def _UNUSED_evalSed(line, sedStr): """ Mimic sed. Example: To mimic Perl `$str =~ s@\s+@ @gs;` do `evalSed(str, "s@\s+@ @gs")`. For m/, use re.findall() instead. """ # - See # - See return os.popen('echo "{}" | sed "{}"'.format(line, sedStr)).read() PURPOSE = 'Linux Minetest build script' REVISION = '200522' # version of this script, not linux-minetest-kit PROFILE_DIR = None HOME_V = "HOME" if platform.system() == "Windows": HOME_V = "USERPROFILE" PROFILE_DIR = os.environ.get(HOME_V) EXTRACTED_DIR = None if os.path.isdir("mtsrc"): EXTRACTED_DIR = os.getcwd() elif PROFILE_DIR is not None: EXTRACTED_DIR = os.path.join(PROFILE_DIR, ".config", "EnlivenMinetest", "linux-minetest-kit") if not os.path.isdir(EXTRACTED_DIR): print("You must run this from the directory of the extracted") print("linux-minetest-kit or have {}".format(EXTRACTED_DIR)) exit(1) else: if not os.path.isdir(EXTRACTED_DIR): print("You must run this from the directory of the extracted" "linux-minetest-kit or have a {} variable" "".format(HOME_V)) exit(1) GITURL = 'http://git.minetest.org/minetest/minetest.git' IE = 'Internal error' Flags = {} # ---------------------------------------------------------------------- # global variables # ---------------------------------------------------------------------- PROGNAME = None # Program name without path DirStack = [os.getcwd()] # Directory stack # ---------------------------------------------------------------------- # used by "--oldproto" # ---------------------------------------------------------------------- segment = """ set(VERSION_MAJOR 0) set(VERSION_MINOR 4) set(VERSION_PATCH 17) set(VERSION_TWEAK 1) set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string") # Change to false for releases set(DEVELOPMENT_BUILD False) set(VERSION_STRING "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}.${VERSION_TWEAK}") if VERSION_EXTRA: set(VERSION_STRING ${VERSION_STRING}-${VERSION_EXTRA}) elseif(DEVELOPMENT_BUILD) set(VERSION_STRING "${VERSION_STRING}-dev") endif() if CMAKE_BUILD_TYPE STREQUAL Debug: """ # ---------------------------------------------------------------------- # low-level utility routines # ---------------------------------------------------------------------- def pushd(path): if not os.path.isdir(path): print("[pushd] ERROR: \"{}\" does not exist.".format(path)) exit(1) os.chdir(path) DirStack.append(path) def popd(): if len(DirStack) < 2: print("[popd] ERROR: only the original path is on the stack") print(" (you popped more than you pushed).") exit(1) else: del DirStack[-1] os.chdir(DirStack[-1]) def execute(cmd, shell=True): """ Iterate output of a command. See tokland's Dec 11, 2010 answer on """ popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True, shell=shell) for stdout_line in iter(popen.stdout.readline, ""): yield stdout_line popen.stdout.close() return_code = popen.wait() if return_code: raise subprocess.CalledProcessError(return_code, cmd) def RunCmd(cmdParts, shell=True): """ :raises RuntimeError: The process returns a non-zero exit code. """ # Popen can get confused when arguments contain quotes # (see ) such as # in "-DCMAKE_CXX_FLAG="+XCFLAGS where XCFLAGS contains quotes. # child = subprocess.Popen(cmdParts, shell=shell, # stdout=subprocess.PIPE, # universal_newlines=True) # streamdata = child.communicate()[0] # rc = child.returncode # Instead of communicate, read the lines (See def execute) # if rc != 0: # if exitOnFail: # exit(rc) # else: # print("WARNING: {} failed".format(' '.join(cmdParts))) try: for line in execute(cmdParts, shell=shell): print(line, end="") except subprocess.CalledProcessError as ex: msg = ("The process '{}' failed with error code {}." "".format(" ".join(ex.cmd), ex.returncode)) raise RuntimeError(msg) def FixStr(s): if s is None: return "" s = re.sub("\s+", " ", s).strip() return s.strip() def GetProgDir(): """ Set the PROGNAME global to this script's name and find the current working directory in terms of its real path (following symbolic links). This differs from mtcompile-program.pl in that this version gets the current directory, so that this script doesn't have to be in the linux-minetest-kit directory. However, due to that, the current directory must be linux-minetest-kit when running this function (when running this script). """ global PROGNAME PROGNAME = os.path.basename(__file__) return os.path.realpath(os.getcwd()) def GetOptions(nonBoolNames=[], bareArgs=[]): """ Convert command-line arguments to values in the global Flags dict. Keyword Arguments: nonBoolNames -- Specify which arguments can have values. All others are considered boolean, and will end the program if they contain '='. bareArgs -- allowed arguments without "--" at the beginning. Returns: false if arg has an equal sign and preceding part after "--" is not in nonBoolNames. """ for i in range(1, len(sys.argv)): arg = sys.argv[i] if (arg in bareArgs) or arg.startswith("--"): start = 0 if arg.startswith("--"): start = 2 signI = arg.find("=") name = arg[start:] val = True if signI > -1: name = arg[start:signI] if name not in nonBoolNames: print("Only the following options can have values:" " {}.".format(nonBoolNames)) return False val = arg[signI+1:] # if TitleCaseAndFlag: # name = "Flag" + name.title() print("* {} = {}".format(name, val)) Flags[name] = val else: print("The option is unknown: {}".format(arg)) return False return True USAGE_FMT = """ #{PROGNAME} {REVISION} - {PURPOSE} #{USAGE_TEXT} """ def UsageText(msg=""): """ "UsageText" prints usage text for the current program, then termin- ates the program with exit status one. """ # NOTE: PROGNAME is this script's name, NOT the engine name. THIS_USAGE = USAGE_FMT.format(PROGNAME=PROGNAME, REVISION=REVISION, PURPOSE=PURPOSE, USAGE_TEXT=USAGE_TEXT.format( PROGNAME=PROGNAME )) # USAGE_TEXT = evalSed('s@\s*\z@\n@s') print(THIS_USAGE) print("") print(msg) print("") print("") exit(1) def main(): # ------------------------------------------------------------------ # Misc. variables. #my $cmd; # Shell command string #my $tmpStr; # Scratch # ------------------------------------------------------------------ # Mode flags. # These may be modified, indirectly, by command-line switches. MAKEDEBUG = False PORTABLE = False TIDYUP = True MAKEPROD = False #--------------------------------------------------------------------- # Command-line option flags. Flags["build"] = False Flags["client"] = False Flags["debug"] = False Flags["edgy"] = False Flags["gitpull"] = False Flags["gitreset"] = False Flags["help"] = False Flags["makeprod"] = False Flags["fakemt4"] = False Flags["noclean"] = False Flags["portable"] = False Flags["postgres"] = False Flags["redis"] = False Flags["safe"] = False Flags["server"] = False Flags["oldproto"] = True # ------------------------------------------------------------------ # Initial setup. #select STDERR; $| = ONE; # Force STDERR flush on write #select STDOUT; $| = ONE; # Force STDOUT flush on write # ------------------------------------------------------------------ # Get absolute path for script directory. # As a side effect, this function call initializes the global variable # "$PROGNAME". Note that this must be done before "UsageText" is call- # ed. THISDIR = GetProgDir() # ------------------------------------------------------------------ # Parse command-line arguments. if not GetOptions(nonBoolNames=["MT_SRC"], bareArgs=["build"]): UsageText() mapLongArgs = {} mapLongArgs["edgytest"] = "edgy" mapLongArgs["notidy"] = "noclean" mapLongArgs["oldprotocol"] = "oldproto" mapLongArgs["postgresql"] = "postgres" mapLongArgs["usage"] = "help" for k, v in mapLongArgs.items(): old_v = Flags.get(k) if old_v is not None: Flags[v] = old_v del Flags[k] # Handle usage-text exit if (not Flags["build"]) or Flags["help"]: if not Flags["build"]: msg = ("You did not specify build or --build (got:{})." "".format(Flags)) UsageText(msg=msg) # ------------------------------------------------------------------ # Handle misc. flag issues. if Flags["edgy"]: Flags["fakemt4"] = True if Flags["edgy"] or Flags["fakemt4"]: Flags["oldproto"] = True if Flags["edgy"] and Flags["gitpull"]: customExit("Error: Can't use both --edgy and --gitpull") # ------------------------------------------------------------------ # Confirm that script is running in the right place. if not os.path.isdir('mtsrc'): echo0(""" Error: This script should be stored, and executed, in the directory which contains the "mtsrc" directory. """) exit(1) # ------------------------------------------------------------------ # Additional directory paths. BALLDIR = os.path.join(THISDIR, "mtsrc", "newline") PRODDIR = os.path.join(THISDIR, "minetest") TOOLS_PREFIX = os.path.join(THISDIR, "toolstree") BINDIR = os.path.join(TOOLS_PREFIX, "bin") INCDIR = os.path.join(TOOLS_PREFIX, "include") LIBDIR = os.path.join(TOOLS_PREFIX, "lib") LIB64DIR = os.path.join(TOOLS_PREFIX, "lib64") # ------------------------------------------------------------------ # Misc. setup. if not BINDIR in os.environ["PATH"]: os.environ["PATH"] = BINDIR + os.pathsep + os.environ["PATH"] which("g++") # ------------------------------------------------------------------ # Handle some of the option flags. if Flags["debug"]: MAKEDEBUG = True if Flags["makeprod"]: MAKEPROD = True if Flags["noclean"]: TIDYUP = False if MAKEPROD: MAKEDEBUG = False PORTABLE = True TIDYUP = True # ------------------------------------------------------------------ # Handle "--gitreset". if Flags["gitreset"]: if Flags["gitpull"]: customExit("Error: Can't use both --gitreset and --gitpull\n") if Flags["safe"] and os.path.isdir('minetest'): print(""" Error: "minetest" directory exists and "--gitreset" needs to delete it. But can't because "--safe" was specified. If you wish to proceed, move or rename the directory. """) exit(1); TIDYUP = False print(""" * --gitreset specified and --safe not specified * Removing any existing "minetest" directory """) shutil.rmtree("minetest") print("* Attempting a git clone...") cmdParts = ["git", "clone", GITURL, "minetest"] print(" " + " ".join(cmdParts)) RunCmd(cmdParts); # ------------------------------------------------------------------ # Handle "--gitpull". if Flags["gitpull"]: if Flags["gitreset"]: customExit("Error: Can't use both --gitreset and --gitpull\n") TIDYUP = False if os.path.isdir('minetest'): if not os.path.isdir('minetest/.git'): print(""" Error: "--gitpull" specified and I see a "minetest" directory but no "minetest/.git" directory. If you'd like to use "--gitpull", delete, rename, or move the "mine- test" directory. Or you can use "--gitreset" instead. This will delete the directory automatically. """) exit(1) if not os.path.isdir('minetest'): print(""" * "--gitpull" specified but I don't see a "minetest" directory * Attempting a git clone """) cmdParts = ["git", "clone", GITURL, "minetest"] print("cmd " + " ".join(cmdParts)) RunCmd(cmdParts) else: if not os.path.isdir(os.path.join("minetest", ".git")): customExit(IE + "#250458") print(""" * --gitpull specified and I see "minetest/.git" * Attempting a git pull """) pushd('minetest'); cmdParts = ["git", "pull"] RunCmd(cmdParts, exit_on_fail=false) popd(); # ------------------------------------------------------------------ # Handle "--client" and "--server". client_line = "-DBUILD_CLIENT=1" server_line = "-DBUILD_SERVER=1" if Flags["client"] and not Flags["server"]: client_line = "-DBUILD_CLIENT=1" server_line = "-DBUILD_SERVER=0" if not Flags["client"] and Flags["server"]: client_line = "-DBUILD_CLIENT=0" server_line = "-DBUILD_SERVER=1" # ------------------------------------------------------------------ # Status messages. NUBDF = "not used by default in this version" print(""" * leveldb (by default) * sqlite3 (by default) """) # ------------------------------------------------------------------ # Handle "--postgres". postgres_line = "-DENABLE_POSTGRESQL=0" if Flags["postgres"]: print("* postgres (due to --postgresql)") postgres_line = "-DENABLE_POSTGRESQL=1" else: print("(skipping postgresql --"+NUBDF) # ------------------------------------------------------------------ # Handle "--redis". redis_line = "-DENABLE_REDIS=0" if Flags["redis"]: print("* redis (due to --redis)") redis_line = "-DENABLE_REDIS=1" else: print("(skipping redis --"+NUBDF) # ------------------------------------------------------------------ # "--portable" requires the bootstrapped "gcc". if PORTABLE and not os.path.isfile(os.path.join(BINDIR, "gcc")): print(""" Error: For Linux portable mode (--portable), you need to build the in- cluded "gcc" 8 compiler. To do so, run "mtcompile-libraries.sh" with gcc-bootstrap mode enabled. """) exit(1) # ------------------------------------------------------------------ # Identify "gcc" major release number. #my ($GCCVER) = $tmpStr =~ m@\ngcc.* (\d+)*\.\d+\.@ tmpStr = subprocess.check_output(["gcc", "--version"]).decode() tmpParts = re.findall("gcc.* (\d+)*\.\d+\.", tmpStr) GCCVER = None if len(tmpParts) > 0: GCCVER = tmpParts[0] else: customExit("Error: Not able to identify gcc release") # ------------------------------------------------------------------ # Replace existing "minetest" directory. RESETDIR = (not os.path.isdir(PRODDIR)) or TIDYUP if RESETDIR: if Flags["safe"] and os.path.isdir('minetest'): print(""" Error: We need to delete the existing "minetest" directory, but "--safe" is specified. If you'd like to preserve the directory, move or rename it. Otherwise, drop the "--safe" switch. """) exit(1) # PRODDIR is THISDIR/minetest print("* cleaning " + os.getcwd()) for sub in os.listdir(THISDIR): sub_path = os.path.join(THISDIR, sub) if sub.startswith("."): continue if os.path.isfile(sub_path): continue if sub_path == PRODDIR: # ^ sub_path must be generated the same way as # PRODDIR (from THISDIR) for this to work (to # remove the linux-minetest-kit/minetest directory). print("* removing old \"{}\"".format(sub_path)) shutil.rmtree(sub_path) elif sub.startswith("minetest-newline"): shutil.rmtree(sub_path) print("* extracting in " + os.getcwd()) mtNewLine = "minetest-newline" if Flags["edgy"]: mtNewLine = "minetest-newline" if Flags.get("MT_SRC") is not None: if not os.path.isdir(Flags["MT_SRC"]): customExit("{} does not exist.".format(Flags["MT_SRC"])) print("* using custom MT_SRC \"{}\" (copying to \"{}\")" "".format(Flags["MT_SRC"], PRODDIR)) shutil.copytree(Flags["MT_SRC"], PRODDIR, copy_function=shutil.copy2) else: tarPath = os.path.join(BALLDIR, mtNewLine + ".tar.bz2") tar = tarfile.open(tarPath) tar.extractall(path=THISDIR) tar.close() for sub in os.listdir(THISDIR): sub_path = os.path.join(THISDIR, sub) if sub.startswith(mtNewLine): print("* using {} as minetest".format(sub_path)) shutil.move(sub_path, PRODDIR) break os.chdir(PRODDIR) # or die "$IE #505850\n"; # ------------------------------------------------------------------ # Sanity check. if not os.path.isfile('CMakeLists.txt'): print(""" Error: You're trying to build using a "minetest" directory that's mis- sing a "CMakeLists.txt". The directory was probably tidied up after a previous build. To rebuild, delete, move, or rename the "minetest" directory and try again. """) exit(1) # ------------------------------------------------------------------ # Delete leftover temporary files. tmpFileNames = [ "C.includecache", "CXX.includecache", "CMakeCache.txt", "CMakeCCompiler.cmake", "CMakeCXXCompiler.cmake", "CMakeDirectoryInformation.cmake", "CMakeRuleHashes.txt", "CPackConfig.cmake", "CPackSourceConfig.cmake", "DependInfo.cmake", "Makefile2", "TargetDirectories.txt", "build.make", "depend.make", "depend.internal", "cmake_config.h", "cmake_install.cmake", "flags.make", "link.txt", "progress.make", "relink.txt" ] tmpFilePaths = [ "textures/base/pack/menu_header_old.png" ] tmpExtensions = [ ".a", ".log", ".o" ] androidSub = os.path.join("build", "android") for root, dirs, files in os.walk(".", topdown=False): for name in files: subPath = os.path.join(root, name) if name in tmpFileNames: os.remove(subPath) elif (name == "Makefile") and (androidSub in subPath): os.remove(subPath) elif endsWithAny(subPath, tmpFilePaths + tmpExtensions): os.remove(subPath) # ------------------------------------------------------------------ # Define paths for some ".a" library files. IRRLICHT_LIBRARY = os.path.join(LIBDIR, "libIrrlicht.a") LEVELDB_LIBRARY = os.path.join(LIBDIR, "libleveldb.a") LUA_LIBRARY = os.path.join(LIBDIR, "libluajit-5.1.a") SQLITE3_LIBRARY = os.path.join(LIBDIR, "libsqlite3.a") # ------------------------------------------------------------------ # Set "$XCFLAGS" (extra compiler flags). XCFLAGS = "-O2 -I" + INCDIR if MAKEDEBUG: XCFLAGS += " -g" if not PORTABLE: XCFLAGS = "-march=native " + XCFLAGS XCFLAGS += " -Wl,-L" + LIBDIR + " -Wl,-R" + LIBDIR if os.path.isdir(LIB64DIR): XCFLAGS += " -Wl,-L" + LIB64DIR + " -Wl,-R" + LIB64DIR print("XCFLAGS="+XCFLAGS) # ------------------------------------------------------------------ # Get pathnames for "gcc" and "g++" compilers. WHICH_GCC = which("gcc") WHICH_GPP = which("g++") if not isExecutableFile(WHICH_GCC): if WHICH_GCC is not None: print("gcc: {}".format(WHICH_GCC)) customExit("gcc is not present or not executable in {}." "".format(os.environ["PATH"])) if not isExecutableFile(WHICH_GPP): if WHICH_GPP is not None: print("g++: {}".format(WHICH_GPP)) customExit("g++ is not present or not executable in {}." "".format(os.environ["PATH"])) # ------------------------------------------------------------------ # Handle another "--edgy step". if Flags["edgy"]: CM = 'src/defaultsettings.cpp' # TODO: finish converting this from perl print("edgy changing {} is not yet implemented." "".format(CM)) data = None try: with open(CM, 'r') as IFD: pass #SS = $/ #undef $/; #data = #data = ""unless defined + data #/ = SS # - secure.enable_security to false by default except FileNotFoundError: customExit("Internal error 0766") # try: # with open(CM) as OFD: # OFD.write(data) # except FileNotFoundError: # customExit("Internal error 0777") # ------------------------------------------------------------------ # Handle "--fakemt4". if Flags["fakemt4"]: CM = 'CMakeLists.txt' print("fakemt4 changing {} is not yet implemented." "".format(CM)) # TODO: handle fakemt4 # data = None # with open(CM, 'r') as IFD: # or die "Internal error 0789\n"; # SS = $/ # undef $/; # data = # data = ""unless defined + data # / = SS # pat = << 'END' # set\(VERSION_MAJOR \d+\) # .*? # if \(CMAKE_BUILD_TYPE STREQUAL Debug\) # END # pat = evalSed('s@\s+\z@@s') # pat = evalSed('s@\s*\n\s*@@gs') # TODO: Use the segment variable here # data = evalSed('s@\s*$pat\s*@\n$segment@is') # with open(CM, 'w') as OFD: # or die "Internal error 0806\n"; # OFD.write(data) # OFD.close() # or die "Internal error 0808\n"; # ------------------------------------------------------------------ # Handle "--oldproto". if Flags["oldproto"]: CM = 'src/network/networkprotocol.h' print("oldproto changing {} is not yet implemented." "".format(CM)) # TODO: change protocol in CM: # data = None # with open(CM, 'r') as IFD: #or die "Internal error 0714\n"; # SS = $/ # undef $/; # data = # data = ""unless defined + data # / = SS # data = evalSed('s@(#define\s+LATEST_PROTOCOL_VERSION)\s+3\d\b@$1 32@') # with open("CM", 'w') as OFD: # or die "Internal error 0715\n"; # OFD.write(data) # ------------------------------------------------------------------ # Run "cmake". cmdParts = ["cmake"] cmdParts.append("-DCMAKE_BUILD_TYPE=release") cmdParts.append("-DCMAKE_C_COMPILER="+WHICH_GCC) cmdParts.append("-DCMAKE_CXX_COMPILER="+WHICH_GPP) cmdParts.append("-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=1") cmdParts.append("-DCMAKE_SKIP_INSTALL_RPATH=0") cmdParts.append("-DCMAKE_SKIP_RPATH=0") cmdParts.append(client_line) cmdParts.append(server_line) cmdParts.append("-DENABLE_LEVELDB=1") cmdParts.append(postgres_line) cmdParts.append(redis_line) cmdParts.append("-DENABLE_SOUND=1") cmdParts.append("-DENABLE_SPATIAL=0") # ^ TODO: WHY 0 in linux-minetest-kit? cmdParts.append("-DENABLE_SYSTEM_JSONCPP=0") cmdParts.append("-DRUN_IN_PLACE=1") cmdParts.append("-DIRRLICHT_INCLUDE_DIR={}/irrlicht".format(INCDIR)) cmdParts.append("-DIRRLICHT_LIBRARY="+IRRLICHT_LIBRARY) cmdParts.append("-DLEVELDB_INCLUDE_DIR={}/leveldb".format(INCDIR)) cmdParts.append("-DLEVELDB_LIBRARY="+LEVELDB_LIBRARY) cmdParts.append("-DLUA_INCLUDE_DIR={}/luajit-2.1".format(INCDIR)) cmdParts.append("-DLUA_LIBRARY="+LUA_LIBRARY) cmdParts.append("-DSQLITE3_INCLUDE_DIR="+INCDIR) cmdParts.append("-DSQLITE3_LIBRARY="+SQLITE3_LIBRARY) cmdParts.append("-DCMAKE_C_FLAGS=\"{}\"".format(XCFLAGS)) cmdParts.append("-DCMAKE_CXX_FLAGS=\"{}\"".format(XCFLAGS)) cmdParts.append("-DCMAKE_C_FLAGS_RELEASE=\"{}\"".format(XCFLAGS)) cmdParts.append("-DCMAKE_CXX_FLAGS_RELEASE=\"{}\"".format(XCFLAGS)) cmdParts.append(".") print("") print("") print("* running cmake in {}:".format(os.getcwd())) print(" ".join(cmdParts)) print("") print("") # RunCmd(cmdParts, shell=True) # ^ fails due to excessive automatic quotes around params by python # RunCmd(cmdParts[:1] + [" ".join(cmdParts[1:])], shell=False) # ^ must be false to avoid inserting quotes automatically # ^ still has problems os.system(" ".join(cmdParts)) # TODO: use some absolute pathnames as the Perl version does # ------------------------------------------------------------------ # Replace some "-l..." switches with absolute pathnames. replacements = { "-lIrrlicht": IRRLICHT_LIBRARY, "-lleveldb": LEVELDB_LIBRARY, "-lluajit-5.1": LUA_LIBRARY, "-lsqlite3": SQLITE3_LIBRARY, } for root, dirs, files in os.walk(PRODDIR): for name in files: subPath = os.path.join(root, name) if name == "link.txt": streamEdit(subPath, replacements) # ------------------------------------------------------------------ # Build the program. NUMJOBS = 3 print("* running make in {}...".format(os.getcwd())) RunCmd(["make", "clean"]) RunCmd("make", "-j{}".format(NUMJOBS)) serverlistDir = os.path.join(PRODDIR, "client", "serverlist") os.makedirs(serverlistDir, exist_ok=True) os.makedirs("games", exist_ok=True) os.makedirs("worlds", exist_ok=True) dstAKPath = os.path.join(PRODDIR, "arrowkeys.txt") shutil.copy(os.path.join(BALLDIR, "arrowkeys.txt"), dstAKPath) # ------------------------------------------------------------------ # Add preloaded cache. thisCache = os.path.join(PRODDIR, "cache") if os.path.isdir(thisCache): shutil.rmtree(thisCache) tarPath = os.path.join(BALLDIR, "cachemedia.tar.bz2") tar = tarfile.open(tarPath) tar.extractall(path=PRODDIR) tar.close() # ------------------------------------------------------------------ # Add "_games". pushd('games'); cmd = "" gameNames = ["minimal", "amhi_game"] if not Flags["edgy"]: # TODO: What does Flags["edgy"] do here? Does it leave the old # Bucket_Game and do nothing else differently? # See mtcompile-program.pl gameNames.append("Bucket_Game") # gamePaths = [os.path.join(PRODDIR, gName) for gName in gameNames] # print("* purging gamepaths: {}".format(gamePaths)) for gameName in gameNames: gamePath = os.path.join(PRODDIR, gameName) if os.path.isdir(gamePath): print("* replacing {}".format(gamePath)) shutil.rmtree(gamePath) else: print("* adding {}".format(gamePath)) zipPath = os.path.join(BALLDIR, gameName + ".zip") if os.path.isfile(zipPath): zf = ZipFile(zipPath) zf.extractall(gamePath) # pwd means password in this case popd(); # ------------------------------------------------------------------ # Add worlds. # pushd('worlds'); WORLDS_PATH = os.path.join(PRODDIR, "worlds") for worldName in ["Bucket_City", "Wonder_World"]: worldPath = os.path.join(WORLDS_PATH, worldName) tarPath = os.path.join(BALLDIR, worldName + ".taz.bz2") if os.path.isdir(worldPath): if os.path.isfile(tarPath): # Only remove the old one if there is a new one. shutil.rmtree(worldPath) if Flags["edgy"]: continue if os.path.isfile(tarPath): tar = tarfile.open(tarPath) tar.extractall(path=WORLDS_PATH) tar.close() else: print("WARNING: \"{}\" is missing.".format(tarPath)) # popd(); # ------------------------------------------------------------------ # Strip the executable(s). if not MAKEDEBUG: prodBinDir = os.path.join(PRODDIR, "bin") for sub in os.listdir(prodBinDir): if sub.startswith("."): continue sub_path = os.path.join(prodBinDir, sub) if not os.path.isfile(sub_path): continue if sub.startswith("minetest"): try: RunCmd(["strip", sub_path]) except RuntimeError: print("strip (optimizing the executable size)" " failed for {}".format(sub_path)) # ------------------------------------------------------------------ # Additional cleanup. if TIDYUP: tmpNames = ["MakeFile", "build", "debug.txt", "lib", "src"] tmpEndsWith = ["cmake"] tmpStartsWith = ["CMake"] for root, dirs, files in os.walk(".", topdown=False): for name in files: subPath = os.path.join(root, name) if name in tmpNames: if os.path.isfile(subPath): os.remove(subPath) else: shutil.rmtree(subPath) elif endsWithAny(subPath, tmpEndsWith): if os.path.isfile(subPath): os.remove(subPath) else: shutil.rmtree(subPath) elif startsWithAny(subPath, tmpStartsWith): if os.path.isfile(subPath): os.remove(subPath) else: shutil.rmtree(subPath) keepWith = [" ", ".dummy"] for root, dirs, files in os.walk(".", topdown=False): for name in files: subPath = os.path.join(root, name) if containsAny(name, keepWith): continue if not name.startswith("."): continue if name[1:2].upper() != name[1:2]: # .[a-z]\* os.remove(subPath) # -------------------------------------------------------------- # Finish required "--portable" operations. if PORTABLE: M = os.uname().machine BITS = 32 if (M == "i686") else 64 solibPath = os.path.join(PRODDIR, "solib") if os.path.isdir(solibPath): shutil.rmtree(solibPath) tarPath = os.path.join(BALLDIR, "solib{}.tar.bz2".format(BITS)) if os.path.isfile(tarPath): tar = tarfile.open(tarPath) tar.extractall(path=PRODDIR) if not os.path.isdir(solibPath): customExit("ERROR: extracting \"{}\" did not result in" " \"{}\"".format(tarPath, solibPath)) tar.close() else: customExit("ERROR: \"{}\" is missing.".format(tarPath)) # pushd('bin'); for prog in ["minetest", "minetestserver"]: srcWrapPath = os.path.join(BALLDIR, prog+".wrapper") progWrapPath = os.path.join(PRODDIR, "bin", prog) progBinPath = os.path.join(PRODDIR, "bin", prog+".bin") if os.path.isfile(progBinPath): os.remove(progBinPath) shutil.move(progWrapPath, progBinPath) # copy2 mimics `cp -p` (preserve attributes) shutil.copy2(srcWrapPath, progWrapPath) os.chmod(progWrapPath, 0o755) # popd() # -------------------------------------------------------------- if MAKEPROD: # os.chdir(THISDIR) # or die "$IE #505833\n"; ZIPDIR = "minetest-linux" + BITS ZIPDIR_PATH = os.path.join(THISDIR, ZIPDIR) ZIPFILE = ZIPDIR + ".zip" ZIPFILE_PATH = os.path.join(THISDIR, ZIPFILE) if os.path.isdir(ZIPDIR_PATH): shutil.rmtree(ZIPDIR_PATH) if os.path.isfile(ZIPFILE_PATH): os.remove(ZIPFILE_PATH) shutil.move(PRODDIR_PATH, ZIPDIR_PATH) zf = zipfile.ZipFile(ZIPFILE_PATH, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) zipdir(ZIP_PATH, zf) zf.close() # originally zip -ro9q # r: recurse into subdirectories (done by zipdir) # o: make zipfile as old as latest entry (handled below) # 9: compress better # q: quiet latestStamp = None for root, dirs, files in os.walk(ZIPDIR_PATH): for name in files: subPath = os.path.join(root, name) stamp = os.stat("ideas.md").st_mtime if (latestStamp is None) or (stamp > latestStamp): latestStamp = stamp st = os.stat(ZIPFILE_PATH) # See atime = st[stat.ST_ATIME] mtime = st[stat.ST_MTIME] shutil.rmtree(ZIPDIR_PATH) os.utime(ZIPFILE_PATH, (atime, latestStamp)) print("Done\n") # end main return 0 if __name__ == "__main__": sys.exit(main())