summaryrefslogtreecommitdiff
path: root/build_extra/rust/get_rust_ldflags.py
diff options
context:
space:
mode:
Diffstat (limited to 'build_extra/rust/get_rust_ldflags.py')
-rwxr-xr-xbuild_extra/rust/get_rust_ldflags.py144
1 files changed, 144 insertions, 0 deletions
diff --git a/build_extra/rust/get_rust_ldflags.py b/build_extra/rust/get_rust_ldflags.py
new file mode 100755
index 000000000..f45b3ff07
--- /dev/null
+++ b/build_extra/rust/get_rust_ldflags.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python
+# Copyright 2018 Bert Belder <bertbelder@gmail.com>
+# All rights reserved. MIT License.
+
+# The Rust compiler normally builds source code directly into an executable.
+# Internally, object code is produced, and then the (system) linker is called,
+# but this all happens under the covers.
+#
+# However Deno's build system uses it's own linker. For it to successfully
+# produce an executable from rustc-generated object code, it needs to link
+# with a dozen or so "built-in" Rust libraries (as in: not Cargo crates),
+# and we need to tell the linker which and where those .rlibs are.
+#
+# Hard-coding these libraries into the GN configuration isn't possible: the
+# required .rlib files have some sort of hash code in their file name, and their
+# location depends on how Rust is set up, and which toolchain is active.
+#
+# So instead, we have this script: it writes a list of linker options (ldflags)
+# to stdout, separated by newline characters. It is called from `rust.gni` when
+# GN is generating ninja files (it doesn't run in the build phase).
+#
+# There is no official way through which rustc will give us the information
+# we need, so a "back door" is used. We tell `rustc` to compile a (dummy)
+# program, and to use a custom linker. This "linker" doesn't actually link
+# anything; it just dumps it's argv to a temporary file. When rustc is done,
+# this script then reads the linker arguments from that temporary file, and
+# then filters it to remove flags that are irrelevant or undesirable.
+
+import sys
+import os
+from os import path
+import subprocess
+import tempfile
+
+
+def capture_args(argsfile_path):
+ with open(argsfile_path, "wb") as argsfile:
+ argsfile.write("\n".join(sys.argv[1:]))
+
+
+def main():
+ # If ARGSFILE_PATH is set this script is being invoked by rustc, which
+ # thinks we are a linker. All we do now is write our argv to the specified
+ # file and exit. Further processing is done by our grandparent process,
+ # also this script but invoked by gn.
+ argsfile_path = os.getenv("ARGSFILE_PATH")
+ if argsfile_path is not None:
+ return capture_args(argsfile_path)
+
+ # Prepare the environment for rustc.
+ rustc_env = os.environ.copy()
+
+ # We'll capture the arguments rustc passes to the linker by telling it
+ # that this script *is* the linker.
+ # On Posix systems, this file is directly executable thanks to it's shebang.
+ # On Windows, we use a .cmd wrapper file.
+ if os.name == "nt":
+ rustc_linker_base, rustc_linker_ext = path.splitext(__file__)
+ rustc_linker = rustc_linker_base + ".cmd"
+ else:
+ rustc_linker = __file__
+
+ # Make sure that when rustc invokes this script, it uses the same version
+ # of the Python interpreter as we're currently using. On Posix systems this
+ # is done making the Python directory the first element of PATH.
+ # On Windows, the wrapper script uses the PYTHON_EXE environment variable.
+ if os.name == "nt":
+ rustc_env["PYTHON_EXE"] = sys.executable
+ else:
+ python_dir = path.dirname(sys.executable)
+ rustc_env["PATH"] = python_dir + path.pathsep + os.environ["PATH"]
+
+ # Create a temporary file to write captured Rust linker arguments to.
+ # Unfortunately we can't use tempfile.NamedTemporaryFile here, because the
+ # file it creates can't be open in two processes at the same time.
+ argsfile_fd, argsfile_path = tempfile.mkstemp()
+ rustc_env["ARGSFILE_PATH"] = argsfile_path
+
+ try:
+ # Spawn rustc, and make it use this very script as its "linker".
+ rustc_args = ["-Clinker=" + rustc_linker, "-Csave-temps"
+ ] + sys.argv[1:]
+ subprocess.check_call(["rustc"] + rustc_args, env=rustc_env)
+
+ # Read captured linker arguments from argsfile.
+ argsfile_size = os.fstat(argsfile_fd).st_size
+ argsfile_content = os.read(argsfile_fd, argsfile_size)
+ args = argsfile_content.split("\n")
+
+ finally:
+ # Close and delete the temporary file.
+ os.close(argsfile_fd)
+ os.unlink(argsfile_path)
+
+ # From the list of captured linker arguments, build the list of ldflags that
+ # we actually need.
+ ldflags = []
+ next_arg_is_flag_value = False
+ for arg in args:
+ # Note that within the following if/elif blocks, `pass` means that
+ # that captured arguments gets included in `ldflags`. The final `else`
+ # clause filters out unrecognized/unwanted flags.
+ if next_arg_is_flag_value:
+ # We're looking at a value that follows certain parametric flags,
+ # e.g. the path in '-L <path>'.
+ next_arg_is_flag_value = False
+ elif arg.endswith(".rlib"):
+ # Built-in Rust library, e.g. `libstd-8524caae8408aac2.rlib`.
+ pass
+ elif arg.endswith(".crate.allocator.rcgu.o"):
+ # This file is needed because it contains certain allocator
+ # related symbols (e.g. `__rust_alloc`, `__rust_oom`).
+ # The Rust compiler normally generates this file just before
+ # linking an executable. We pass `-Csave-temps` to rustc so it
+ # doesn't delete the file when it's done linking.
+ pass
+ elif arg.endswith(".lib") and not arg.startswith("msvcrt"):
+ # Include most Windows static/import libraries (e.g. `ws2_32.lib`).
+ # However we ignore Rusts choice of C runtime (`mvcrt*.lib`).
+ # Rust insists on always using the release "flavor", even in debug
+ # mode, which causes conflicts with other libraries we link with.
+ pass
+ elif arg.upper().startswith("/LIBPATH:"):
+ # `/LIBPATH:<path>`: Linker search path (Microsoft style).
+ pass
+ elif arg == "-l" or arg == "-L":
+ # `-l <name>`: Link with library (GCC style).
+ # `-L <path>`: Linker search path (GCC style).
+ next_arg_is_flag_value = True # Ensure flag argument is captured.
+ elif arg == "-Wl,--start-group" or arg == "-Wl,--end-group":
+ # Start or end of an archive group (GCC style).
+ pass
+ else:
+ # Not a flag we're interested in -- don't add it to ldflags.
+ continue
+
+ ldflags += [arg]
+
+ # Write the filtered ldflags to stdout, separated by newline characters.
+ sys.stdout.write("\n".join(ldflags))
+
+
+if __name__ == '__main__':
+ sys.exit(main())