On Wed, Apr 14, 2021 at 11:48 AM <ojeda@xxxxxxxxxx> wrote: > > From: Miguel Ojeda <ojeda@xxxxxxxxxx> > > This commit includes also the `Kconfig` entries related to Rust, > the Rust configuration printer, the target definition files, > the version detection script and a few other bits. > > In the future, we will likely want to generate the target files > on the fly via a script. > > With this in place, we will be adding the rest of the bits piece > by piece and enabling their builds. > > Co-developed-by: Alex Gaynor <alex.gaynor@xxxxxxxxx> > Signed-off-by: Alex Gaynor <alex.gaynor@xxxxxxxxx> > Co-developed-by: Geoffrey Thomas <geofft@xxxxxxxxxxxxx> > Signed-off-by: Geoffrey Thomas <geofft@xxxxxxxxxxxxx> > Co-developed-by: Finn Behrens <me@xxxxxxxxx> > Signed-off-by: Finn Behrens <me@xxxxxxxxx> > Co-developed-by: Adam Bratschi-Kaye <ark.email@xxxxxxxxx> > Signed-off-by: Adam Bratschi-Kaye <ark.email@xxxxxxxxx> > Co-developed-by: Wedson Almeida Filho <wedsonaf@xxxxxxxxxx> > Signed-off-by: Wedson Almeida Filho <wedsonaf@xxxxxxxxxx> > Co-developed-by: Michael Ellerman <mpe@xxxxxxxxxxxxxx> > Signed-off-by: Michael Ellerman <mpe@xxxxxxxxxxxxxx> > Signed-off-by: Miguel Ojeda <ojeda@xxxxxxxxxx> > --- > .gitignore | 2 + > .rustfmt.toml | 12 +++ > Documentation/kbuild/kbuild.rst | 4 + > Documentation/process/changes.rst | 9 ++ > Makefile | 120 +++++++++++++++++++++++-- > arch/arm64/rust/target.json | 40 +++++++++ > arch/powerpc/rust/target.json | 30 +++++++ > arch/x86/rust/target.json | 42 +++++++++ > init/Kconfig | 27 ++++++ > lib/Kconfig.debug | 100 +++++++++++++++++++++ > rust/.gitignore | 5 ++ > rust/Makefile | 141 ++++++++++++++++++++++++++++++ > scripts/Makefile.build | 19 ++++ > scripts/Makefile.lib | 12 +++ > scripts/kconfig/confdata.c | 67 +++++++++++++- > scripts/rust-version.sh | 31 +++++++ > 16 files changed, 654 insertions(+), 7 deletions(-) > create mode 100644 .rustfmt.toml > create mode 100644 arch/arm64/rust/target.json > create mode 100644 arch/powerpc/rust/target.json > create mode 100644 arch/x86/rust/target.json > create mode 100644 rust/.gitignore > create mode 100644 rust/Makefile > create mode 100755 scripts/rust-version.sh > > diff --git a/.gitignore b/.gitignore > index 3af66272d6f1..6ba4f516f46c 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -37,6 +37,7 @@ > *.o > *.o.* > *.patch > +*.rmeta > *.s > *.so > *.so.dbg > @@ -96,6 +97,7 @@ modules.order > !.gitattributes > !.gitignore > !.mailmap > +!.rustfmt.toml > > # > # Generated include files > diff --git a/.rustfmt.toml b/.rustfmt.toml > new file mode 100644 > index 000000000000..5893c0e3cbde > --- /dev/null > +++ b/.rustfmt.toml > @@ -0,0 +1,12 @@ > +edition = "2018" > +format_code_in_doc_comments = true > +newline_style = "Unix" > + > +# Unstable options that help catching some mistakes in formatting and that we may want to enable > +# when they become stable. > +# > +# They are kept here since they are useful to run from time to time. > +#reorder_impl_items = true > +#comment_width = 100 > +#wrap_comments = true > +#normalize_comments = true > diff --git a/Documentation/kbuild/kbuild.rst b/Documentation/kbuild/kbuild.rst > index 2d1fc03d346e..1109d18d9377 100644 > --- a/Documentation/kbuild/kbuild.rst > +++ b/Documentation/kbuild/kbuild.rst > @@ -57,6 +57,10 @@ CFLAGS_MODULE > ------------- > Additional module specific options to use for $(CC). > > +KRUSTCFLAGS > +----------- > +Additional options to the Rust compiler (for built-in and modules). > + > LDFLAGS_MODULE > -------------- > Additional options used for $(LD) when linking modules. > diff --git a/Documentation/process/changes.rst b/Documentation/process/changes.rst > index dac17711dc11..4b6ba5458706 100644 > --- a/Documentation/process/changes.rst > +++ b/Documentation/process/changes.rst > @@ -31,6 +31,8 @@ you probably needn't concern yourself with pcmciautils. > ====================== =============== ======================================== > GNU C 4.9 gcc --version > Clang/LLVM (optional) 10.0.1 clang --version > +rustc (optional) nightly rustc --version > +bindgen (optional) 0.56.0 bindgen --version > GNU make 3.81 make --version > binutils 2.23 ld -v > flex 2.5.35 flex --version > @@ -56,6 +58,7 @@ iptables 1.4.2 iptables -V > openssl & libcrypto 1.0.0 openssl version > bc 1.06.95 bc --version > Sphinx\ [#f1]_ 1.3 sphinx-build --version > +rustdoc (optional) nightly rustdoc --version > ====================== =============== ======================================== > > .. [#f1] Sphinx is needed only to build the Kernel documentation > @@ -330,6 +333,12 @@ Sphinx > Please see :ref:`sphinx_install` in :ref:`Documentation/doc-guide/sphinx.rst <sphinxdoc>` > for details about Sphinx requirements. > > +rustdoc > +------- > + > +``rustdoc`` is used to generate Rust documentation. Please see > +:ref:`Documentation/rust/docs.rst <rust_docs>` for more information. > + > Getting updated software > ======================== > > diff --git a/Makefile b/Makefile > index 9c75354324ed..62b3bba38635 100644 > --- a/Makefile > +++ b/Makefile > @@ -120,6 +120,13 @@ endif > > export KBUILD_CHECKSRC > > +# Enable "clippy" (a linter) as part of the Rust compilation. > +# > +# Use 'make CLIPPY=1' to enable it. > +ifeq ("$(origin CLIPPY)", "command line") > + KBUILD_CLIPPY := $(CLIPPY) > +endif Rather than check the origin (yikes, are we intentionally avoiding env vars?), can this simply be ifneq ($(CLIPPY),) KBUILD_CLIPPY := $(CLIPPY) endif Then you can specify whatever value you want, support command line or env vars, etc.? > + > # Use make M=dir or set the environment variable KBUILD_EXTMOD to specify the > # directory of external module to build. Setting M= takes precedence. > ifeq ("$(origin M)", "command line") > @@ -263,7 +270,7 @@ no-dot-config-targets := $(clean-targets) \ > cscope gtags TAGS tags help% %docs check% coccicheck \ > $(version_h) headers headers_% archheaders archscripts \ > %asm-generic kernelversion %src-pkg dt_binding_check \ > - outputmakefile > + outputmakefile rustfmt rustfmtcheck > no-sync-config-targets := $(no-dot-config-targets) %install kernelrelease \ > image_name > single-targets := %.a %.i %.ko %.lds %.ll %.lst %.mod %.o %.s %.symtypes %/ > @@ -444,6 +451,10 @@ OBJDUMP = $(CROSS_COMPILE)objdump > READELF = $(CROSS_COMPILE)readelf > STRIP = $(CROSS_COMPILE)strip > endif > +RUSTC = rustc > +RUSTFMT = rustfmt > +CLIPPY_DRIVER = clippy-driver > +BINDGEN = bindgen > PAHOLE = pahole > RESOLVE_BTFIDS = $(objtree)/tools/bpf/resolve_btfids/resolve_btfids > LEX = flex > @@ -467,9 +478,11 @@ CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \ > -Wbitwise -Wno-return-void -Wno-unknown-attribute $(CF) > NOSTDINC_FLAGS := > CFLAGS_MODULE = > +RUSTCFLAGS_MODULE = > AFLAGS_MODULE = > LDFLAGS_MODULE = > CFLAGS_KERNEL = > +RUSTCFLAGS_KERNEL = > AFLAGS_KERNEL = > LDFLAGS_vmlinux = > > @@ -498,15 +511,30 @@ KBUILD_CFLAGS := -Wall -Wundef -Werror=strict-prototypes -Wno-trigraphs \ > -Werror=return-type -Wno-format-security \ > -std=gnu89 > KBUILD_CPPFLAGS := -D__KERNEL__ > +KBUILD_RUSTCFLAGS := --emit=dep-info,obj,metadata --edition=2018 \ > + -Cpanic=abort -Cembed-bitcode=n -Clto=n -Crpath=n \ > + -Cforce-unwind-tables=n -Ccodegen-units=1 \ > + -Zbinary_dep_depinfo=y -Zsymbol-mangling-version=v0 > KBUILD_AFLAGS_KERNEL := > KBUILD_CFLAGS_KERNEL := > +KBUILD_RUSTCFLAGS_KERNEL := > KBUILD_AFLAGS_MODULE := -DMODULE > KBUILD_CFLAGS_MODULE := -DMODULE > +KBUILD_RUSTCFLAGS_MODULE := --cfg MODULE > KBUILD_LDFLAGS_MODULE := > KBUILD_LDFLAGS := > CLANG_FLAGS := > > -export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC > +ifeq ($(KBUILD_CLIPPY),1) > + RUSTC_OR_CLIPPY_QUIET := CLIPPY > + RUSTC_OR_CLIPPY = $(CLIPPY_DRIVER) > +else > + RUSTC_OR_CLIPPY_QUIET := RUSTC > + RUSTC_OR_CLIPPY = $(RUSTC) > +endif > +export RUSTC_OR_CLIPPY_QUIET RUSTC_OR_CLIPPY > + > +export ARCH SRCARCH CONFIG_SHELL BASH HOSTCC KBUILD_HOSTCFLAGS CROSS_COMPILE LD CC RUSTC BINDGEN > export CPP AR NM STRIP OBJCOPY OBJDUMP READELF PAHOLE RESOLVE_BTFIDS LEX YACC AWK INSTALLKERNEL > export PERL PYTHON3 CHECK CHECKFLAGS MAKE UTS_MACHINE HOSTCXX > export KGZIP KBZIP2 KLZOP LZMA LZ4 XZ ZSTD > @@ -514,9 +542,10 @@ export KBUILD_HOSTCXXFLAGS KBUILD_HOSTLDFLAGS KBUILD_HOSTLDLIBS LDFLAGS_MODULE > > export KBUILD_CPPFLAGS NOSTDINC_FLAGS LINUXINCLUDE OBJCOPYFLAGS KBUILD_LDFLAGS > export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE > +export KBUILD_RUSTCFLAGS RUSTCFLAGS_KERNEL RUSTCFLAGS_MODULE > export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE > -export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE > -export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL > +export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_RUSTCFLAGS_MODULE KBUILD_LDFLAGS_MODULE > +export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL KBUILD_RUSTCFLAGS_KERNEL > > # Files to ignore in find ... statements > > @@ -712,7 +741,7 @@ $(KCONFIG_CONFIG): > quiet_cmd_syncconfig = SYNC $@ > cmd_syncconfig = $(MAKE) -f $(srctree)/Makefile syncconfig > > -%/config/auto.conf %/config/auto.conf.cmd %/generated/autoconf.h: $(KCONFIG_CONFIG) > +%/config/auto.conf %/config/auto.conf.cmd %/generated/autoconf.h %/generated/rustc_cfg: $(KCONFIG_CONFIG) > +$(call cmd,syncconfig) > else # !may-sync-config > # External modules and some install targets need include/generated/autoconf.h > @@ -738,12 +767,43 @@ KBUILD_CFLAGS += $(call cc-disable-warning, format-truncation) > KBUILD_CFLAGS += $(call cc-disable-warning, format-overflow) > KBUILD_CFLAGS += $(call cc-disable-warning, address-of-packed-member) > > +ifdef CONFIG_RUST_DEBUG_ASSERTIONS > +KBUILD_RUSTCFLAGS += -Cdebug-assertions=y > +else > +KBUILD_RUSTCFLAGS += -Cdebug-assertions=n > +endif > + > +ifdef CONFIG_RUST_OVERFLOW_CHECKS > +KBUILD_RUSTCFLAGS += -Coverflow-checks=y > +else > +KBUILD_RUSTCFLAGS += -Coverflow-checks=n > +endif > + > ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE > KBUILD_CFLAGS += -O2 > +KBUILD_RUSTCFLAGS_OPT_LEVEL_MAP := 2 > else ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3 > KBUILD_CFLAGS += -O3 > +KBUILD_RUSTCFLAGS_OPT_LEVEL_MAP := 3 > else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE > KBUILD_CFLAGS += -Os > +KBUILD_RUSTCFLAGS_OPT_LEVEL_MAP := z -Oz in clang typically generates larger kernel code than -Os; LLVM seems to aggressively emit libcalls even when the setup for a call would be larger than the inlined call itself. Is z smaller than s for the existing rust examples? > +endif > + > +ifdef CONFIG_RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C > +KBUILD_RUSTCFLAGS += -Copt-level=$(KBUILD_RUSTCFLAGS_OPT_LEVEL_MAP) > +else ifdef CONFIG_RUST_OPT_LEVEL_0 > +KBUILD_RUSTCFLAGS += -Copt-level=0 > +else ifdef CONFIG_RUST_OPT_LEVEL_1 > +KBUILD_RUSTCFLAGS += -Copt-level=1 > +else ifdef CONFIG_RUST_OPT_LEVEL_2 > +KBUILD_RUSTCFLAGS += -Copt-level=2 > +else ifdef CONFIG_RUST_OPT_LEVEL_3 > +KBUILD_RUSTCFLAGS += -Copt-level=3 > +else ifdef CONFIG_RUST_OPT_LEVEL_S > +KBUILD_RUSTCFLAGS += -Copt-level=s > +else ifdef CONFIG_RUST_OPT_LEVEL_Z > +KBUILD_RUSTCFLAGS += -Copt-level=z This is a mess; who thought it would be a good idea to support compiling the rust code at a different optimization level than the rest of the C code in the kernel? Do we really need that flexibility for Rust kernel code, or can we drop this feature? > endif > > # Tell gcc to never replace conditional load with a non-conditional one > @@ -793,6 +853,7 @@ endif > KBUILD_CFLAGS += $(call cc-disable-warning, unused-const-variable) > ifdef CONFIG_FRAME_POINTER > KBUILD_CFLAGS += -fno-omit-frame-pointer -fno-optimize-sibling-calls > +KBUILD_RUSTCFLAGS += -Cforce-frame-pointers=y Don't the target.json files all set `"eliminate-frame-pointer": false,`? Is this necessary then? Also, which targets retain frame pointers at which optimization levels is quite messy (in C compilers), as well as your choice of unwinder, which is configurable for certain architectures. > else > # Some targets (ARM with Thumb2, for example), can't be built with frame > # pointers. For those, we don't have FUNCTION_TRACER automatically > @@ -826,6 +887,8 @@ ifdef CONFIG_CC_IS_GCC > DEBUG_CFLAGS += $(call cc-ifversion, -lt, 0500, $(call cc-option, -fno-var-tracking-assignments)) > endif > > +DEBUG_RUSTCFLAGS := > + > ifdef CONFIG_DEBUG_INFO > > ifdef CONFIG_DEBUG_INFO_SPLIT > @@ -836,6 +899,11 @@ endif > > ifneq ($(LLVM_IAS),1) > KBUILD_AFLAGS += -Wa,-gdwarf-2 > +ifdef CONFIG_DEBUG_INFO_REDUCED > +DEBUG_RUSTCFLAGS += -Cdebuginfo=1 > +else > +DEBUG_RUSTCFLAGS += -Cdebuginfo=2 > +endif > endif This should not be in the `ifneq ($(LLVM_IAS),1)` block, otherwise if I set `LLVM_IAS=1` when invoking `make` then these wont get set. > > ifndef CONFIG_DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT > @@ -860,6 +928,9 @@ endif # CONFIG_DEBUG_INFO > KBUILD_CFLAGS += $(DEBUG_CFLAGS) > export DEBUG_CFLAGS > > +KBUILD_RUSTCFLAGS += $(DEBUG_RUSTCFLAGS) > +export DEBUG_RUSTCFLAGS > + > ifdef CONFIG_FUNCTION_TRACER > ifdef CONFIG_FTRACE_MCOUNT_USE_CC > CC_FLAGS_FTRACE += -mrecord-mcount > @@ -990,10 +1061,11 @@ include $(addprefix $(srctree)/, $(include-y)) > # Do not add $(call cc-option,...) below this line. When you build the kernel > # from the clean source tree, the GCC plugins do not exist at this point. > > -# Add user supplied CPPFLAGS, AFLAGS and CFLAGS as the last assignments > +# Add user supplied CPPFLAGS, AFLAGS, CFLAGS and RUSTCFLAGS as the last assignments > KBUILD_CPPFLAGS += $(KCPPFLAGS) > KBUILD_AFLAGS += $(KAFLAGS) > KBUILD_CFLAGS += $(KCFLAGS) > +KBUILD_RUSTCFLAGS += $(KRUSTCFLAGS) I can't help but read that as Krusty-flags. :P Are ya' ready kids? > > KBUILD_LDFLAGS_MODULE += --build-id=sha1 > LDFLAGS_vmlinux += --build-id=sha1 > @@ -1138,6 +1210,10 @@ export MODULES_NSDEPS := $(extmod-prefix)modules.nsdeps > ifeq ($(KBUILD_EXTMOD),) > core-y += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ block/ > > +ifdef CONFIG_RUST > +core-y += rust/ > +endif > + > vmlinux-dirs := $(patsubst %/,%,$(filter %/, \ > $(core-y) $(core-m) $(drivers-y) $(drivers-m) \ > $(libs-y) $(libs-m))) > @@ -1238,6 +1314,9 @@ archprepare: outputmakefile archheaders archscripts scripts include/config/kerne > prepare0: archprepare > $(Q)$(MAKE) $(build)=scripts/mod > $(Q)$(MAKE) $(build)=. > +ifdef CONFIG_RUST > + $(Q)$(MAKE) $(build)=rust > +endif > > # All the preparing.. > prepare: prepare0 prepare-objtool prepare-resolve_btfids > @@ -1648,6 +1727,13 @@ help: > @echo ' kselftest-merge - Merge all the config dependencies of' > @echo ' kselftest to existing .config.' > @echo '' > + @echo 'Rust targets:' > + @echo ' rustfmt - Reformat all the Rust code in the kernel' Seems like a good way for drive by commits where someone reformatted the whole kernel. > + @echo ' rustfmtcheck - Checks if all the Rust code in the kernel' > + @echo ' is formatted, printing a diff otherwise.' Might be nice to invoke this somehow from checkpatch.pl somehow for changes to rust source files. Not necessary in the RFC, but perhaps one day. > + @echo ' rustdoc - Generate Rust documentation' > + @echo ' (requires kernel .config)' > + @echo '' > @$(if $(dtstree), \ > echo 'Devicetree:'; \ > echo '* dtbs - Build device tree blobs for enabled boards'; \ > @@ -1719,6 +1805,27 @@ PHONY += $(DOC_TARGETS) > $(DOC_TARGETS): > $(Q)$(MAKE) $(build)=Documentation $@ > > + > +# Rust targets > +# --------------------------------------------------------------------------- > + > +# Documentation target > +# > +# Using the singular to avoid running afoul of `no-dot-config-targets`. > +PHONY += rustdoc > +rustdoc: prepare0 > + $(Q)$(MAKE) $(build)=rust $@ > + > +# Formatting targets > +PHONY += rustfmt rustfmtcheck > + > +rustfmt: > + find -name '*.rs' | xargs $(RUSTFMT) > + > +rustfmtcheck: > + find -name '*.rs' | xargs $(RUSTFMT) --check > + > + > # Misc > # --------------------------------------------------------------------------- > > @@ -1866,6 +1973,7 @@ clean: $(clean-dirs) > $(call cmd,rmfiles) > @find $(if $(KBUILD_EXTMOD), $(KBUILD_EXTMOD), .) $(RCS_FIND_IGNORE) \ > \( -name '*.[aios]' -o -name '*.ko' -o -name '.*.cmd' \ > + -o -name '*.rmeta' \ > -o -name '*.ko.*' \ > -o -name '*.dtb' -o -name '*.dtbo' -o -name '*.dtb.S' -o -name '*.dt.yaml' \ > -o -name '*.dwo' -o -name '*.lst' \ > diff --git a/arch/arm64/rust/target.json b/arch/arm64/rust/target.json > new file mode 100644 > index 000000000000..44953e2725c4 > --- /dev/null > +++ b/arch/arm64/rust/target.json > @@ -0,0 +1,40 @@ > +{ > + "arch": "aarch64", > + "data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128", > + "disable-redzone": true, > + "eliminate-frame-pointer": false, > + "emit-debug-gdb-scripts": false, > + "env": "gnu", > + "features": "+strict-align,+neon,+fp-armv8", > + "function-sections": false, ^ Depends on your config. This is implied by LTO, and IIRC CONFIG_LD_DEAD_CODE_ELIMINATION does set this. > + "is-builtin": true, > + "linker-flavor": "gcc", > + "linker-is-gnu": true, Is there more documentation on these values? For example, is LLD a GNU linker? I would think not, but perhaps the flag could have been named better and this is a non-issue. > + "llvm-target": "aarch64-unknown-none", What happens if you use aarch64-linux-gnu, like Clang does? > + "max-atomic-width": 128, > + "needs-plt": true, ^ do we? > + "os": "none", > + "panic-strategy": "abort", ^ alternatives? > + "position-independent-executables": true, > + "pre-link-args": { > + "gcc": [ ^ gcc? > + "-Wl,--as-needed", > + "-Wl,-z,noexecstack", > + "-m64" Is -m64 necessary? > + ] > + }, > + "relocation-model": "static", > + "relro-level": "full", > + "stack-probes": { > + "kind": "inline-or-call", > + "min-llvm-version-for-inline": [ > + 11, > + 0, > + 1 > + ] > + }, > + "target-c-int-width": "32", > + "target-endian": "little", The kernel supports both. Does that mean there would be two different aarch64 target.json files, each per endianness? Maybe not critical for initial support, but something to be aware of. > + "target-pointer-width": "64", > + "vendor": "" > +} > diff --git a/arch/powerpc/rust/target.json b/arch/powerpc/rust/target.json > new file mode 100644 > index 000000000000..1e53f8308092 > --- /dev/null > +++ b/arch/powerpc/rust/target.json > @@ -0,0 +1,30 @@ > +{ > + "arch": "powerpc64", > + "code-mode": "kernel", > + "cpu": "ppc64le", > + "data-layout": "e-m:e-i64:64-n32:64", Seems like this target doesn't set "eliminate-frame-pointer." I think it should one way or another to be explicit? > + "env": "gnu", > + "features": "-altivec,-vsx,-hard-float", > + "function-sections": false, > + "is-builtin": true, > + "linker-flavor": "gcc", > + "linker-is-gnu": true, > + "llvm-target": "powerpc64le-elf", > + "max-atomic-width": 64, > + "os": "none", > + "panic-strategy": "abort", > + "position-independent-executables": true, > + "pre-link-args": { > + "gcc": [ > + "-Wl,--as-needed", > + "-Wl,-z,noexecstack", > + "-m64" > + ] > + }, > + "relocation-model": "static", > + "relro-level": "full", > + "target-family": "unix", > + "target-mcount": "_mcount", > + "target-endian": "little", > + "target-pointer-width": "64" > +} > diff --git a/arch/x86/rust/target.json b/arch/x86/rust/target.json > new file mode 100644 > index 000000000000..6e1759cd45bf > --- /dev/null > +++ b/arch/x86/rust/target.json > @@ -0,0 +1,42 @@ > +{ > + "arch": "x86_64", > + "code-model": "kernel", > + "cpu": "x86-64", > + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", > + "disable-redzone": true, > + "eliminate-frame-pointer": false, > + "emit-debug-gdb-scripts": false, > + "env": "gnu", > + "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float", > + "function-sections": false, > + "is-builtin": true, > + "linker-flavor": "gcc", > + "linker-is-gnu": true, > + "llvm-target": "x86_64-elf", > + "max-atomic-width": 64, > + "needs-plt": true, > + "os": "none", > + "panic-strategy": "abort", > + "position-independent-executables": true, > + "pre-link-args": { > + "gcc": [ > + "-Wl,--as-needed", > + "-Wl,-z,noexecstack", > + "-m64" > + ] > + }, > + "relocation-model": "static", > + "relro-level": "full", > + "stack-probes": { > + "kind": "inline-or-call", > + "min-llvm-version-for-inline": [ > + 11, > + 0, > + 1 > + ] > + }, > + "target-c-int-width": "32", > + "target-endian": "little", > + "target-pointer-width": "64", > + "vendor": "unknown" > +} > diff --git a/init/Kconfig b/init/Kconfig > index 5f5c776ef192..11475840c29c 100644 > --- a/init/Kconfig > +++ b/init/Kconfig > @@ -57,6 +57,15 @@ config LLD_VERSION > default $(ld-version) if LD_IS_LLD > default 0 > > +config HAS_RUST > + depends on ARM64 || (PPC64 && CPU_LITTLE_ENDIAN) || X86_64 > + def_bool $(success,$(RUSTC) --version) > + > +config RUSTC_VERSION > + depends on HAS_RUST > + int > + default $(shell,$(srctree)/scripts/rust-version.sh $(RUSTC)) > + > config CC_CAN_LINK > bool > default $(success,$(srctree)/scripts/cc-can-link.sh $(CC) $(CLANG_FLAGS) $(m64-flag)) if 64BIT > @@ -2027,6 +2036,24 @@ config PROFILING > Say Y here to enable the extended profiling support mechanisms used > by profilers. > > +config RUST > + bool "Rust support" > + depends on HAS_RUST > + depends on !COMPILE_TEST > + default n > + help > + Enables Rust support in the kernel. > + > + This allows other Rust-related options, like drivers written in Rust, > + to be selected. > + > + It is also required to be able to load external kernel modules > + written in Rust. > + > + See Documentation/rust/ for more information. > + > + If unsure, say N. > + > # > # Place an empty function call at each tracepoint site. Can be > # dynamically changed for a probe function. > diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug > index 2779c29d9981..acf4993baddc 100644 > --- a/lib/Kconfig.debug > +++ b/lib/Kconfig.debug > @@ -2537,6 +2537,106 @@ config HYPERV_TESTING > > endmenu # "Kernel Testing and Coverage" > > +menu "Rust hacking" > + > +config RUST_DEBUG_ASSERTIONS > + bool "Debug assertions" > + default n > + depends on RUST > + help > + Enables rustc's `-Cdebug-assertions` codegen option. > + > + This flag lets you turn `cfg(debug_assertions)` conditional > + compilation on or off. This can be used to enable extra debugging > + code in development but not in production. For example, it controls > + the behavior of the standard library's `debug_assert!` macro. > + > + Note that this will apply to all Rust code, including `core`. > + > + If unsure, say N. > + > +config RUST_OVERFLOW_CHECKS > + bool "Overflow checks" > + default y > + depends on RUST > + help > + Enables rustc's `-Coverflow-checks` codegen option. > + > + This flag allows you to control the behavior of runtime integer > + overflow. When overflow-checks are enabled, a panic will occur > + on overflow. > + > + Note that this will apply to all Rust code, including `core`. > + > + If unsure, say Y. > + > +choice > + prompt "Optimization level" > + default RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C > + depends on RUST > + help > + Controls rustc's `-Copt-level` codegen option. > + > + This flag controls the optimization level. > + > + If unsure, say "Similar as chosen for C". Yuck. This should be default on and not configurable. > + > +config RUST_OPT_LEVEL_SIMILAR_AS_CHOSEN_FOR_C > + bool "Similar as chosen for C" > + help > + This choice will pick a similar optimization level as chosen in > + the "Compiler optimization level" for C: > + > + -O2 is currently mapped to -Copt-level=2 > + -O3 is currently mapped to -Copt-level=3 > + -Os is currently mapped to -Copt-level=z > + > + The mapping may change over time to follow the intended semantics > + of the choice for C as sensibly as possible. > + > + This is the default. > + > +config RUST_OPT_LEVEL_0 > + bool "No optimizations (-Copt-level=0)" > + help > + Not recommended for most purposes. It may come in handy for debugging > + suspected optimizer bugs, unexpected undefined behavior, etc. > + > + Note that this level will *not* enable debug assertions nor overflow > + checks on its own (like it happens when interacting with rustc > + directly). Use the corresponding configuration options to control > + that instead, orthogonally. > + > +config RUST_OPT_LEVEL_1 > + bool "Basic optimizations (-Copt-level=1)" > + help > + Useful for debugging without getting too lost, but without > + the overhead and boilerplate of no optimizations at all. > + > +config RUST_OPT_LEVEL_2 > + bool "Some optimizations (-Copt-level=2)" > + help > + The sensible choice in most cases. > + > +config RUST_OPT_LEVEL_3 > + bool "All optimizations (-Copt-level=3)" > + help > + Yet more performance (hopefully). > + > +config RUST_OPT_LEVEL_S > + bool "Optimize for size (-Copt-level=s)" > + help > + Smaller kernel, ideally without too much performance loss. > + > +config RUST_OPT_LEVEL_Z > + bool "Optimize for size, no loop vectorization (-Copt-level=z)" > + help > + Like the previous level, but also turn off loop vectorization. > + > +endchoice > + > +endmenu # "Rust" > + > source "Documentation/Kconfig" > > endmenu # Kernel hacking > diff --git a/rust/.gitignore b/rust/.gitignore > new file mode 100644 > index 000000000000..8875e08ed0b1 > --- /dev/null > +++ b/rust/.gitignore > @@ -0,0 +1,5 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +bindings_generated.rs > +exports_*_generated.h > +doc/ > \ No newline at end of file > diff --git a/rust/Makefile b/rust/Makefile > new file mode 100644 > index 000000000000..ba4b13e4fc7f > --- /dev/null > +++ b/rust/Makefile > @@ -0,0 +1,141 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +RUSTDOC = rustdoc > + > +quiet_cmd_rustdoc = RUSTDOC $< > + cmd_rustdoc = \ > + RUST_BINDINGS_FILE=$(abspath $(objtree)/rust/bindings_generated.rs) \ > + $(RUSTDOC) $(filter-out --emit=%, $(rustc_flags)) \ > + $(rustdoc_target_flags) -L $(objtree)/rust/ \ > + --output $(objtree)/rust/doc --crate-name $(subst rustdoc-,,$@) \ > + -Fmissing-docs @$(objtree)/include/generated/rustc_cfg $< > + > +rustdoc: rustdoc-module rustdoc-compiler_builtins rustdoc-kernel > + > +rustdoc-module: private rustdoc_target_flags = --crate-type proc-macro \ > + --extern proc_macro > +rustdoc-module: $(srctree)/rust/module.rs FORCE > + $(call if_changed,rustdoc) > + > +rustdoc-compiler_builtins: $(srctree)/rust/compiler_builtins.rs FORCE > + $(call if_changed,rustdoc) > + > +rustdoc-kernel: private rustdoc_target_flags = --extern alloc \ > + --extern module=$(objtree)/rust/libmodule.so > +rustdoc-kernel: $(srctree)/rust/kernel/lib.rs rustdoc-module \ > + $(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE > + $(call if_changed,rustdoc) > + > +ifdef CONFIG_CC_IS_CLANG > +bindgen_c_flags = $(c_flags) > +else > +# bindgen relies on libclang to parse C. Ideally, bindgen would support a GCC > +# plugin backend and/or the Clang driver would be perfectly compatible with GCC. > +# > +# For the moment, here we are tweaking the flags on the fly. Some config > +# options may not work (e.g. `GCC_PLUGIN_RANDSTRUCT` if we end up using one > +# of those structs). We might want to redo how Clang flags are kept track of > +# in the general `Makefile` even for GCC builds, similar to what we did with > +# `TENTATIVE_CLANG_FLAGS`. > +bindgen_skip_c_flags := -mno-fp-ret-in-387 -mpreferred-stack-boundary=% \ > + -mskip-rax-setup -mgeneral-regs-only -msign-return-address=% \ > + -mindirect-branch=thunk-extern -mindirect-branch-register -mrecord-mcount \ > + -mabi=lp64 -mstack-protector-guard% -fconserve-stack -falign-jumps=% \ > + -falign-loops=% -fno-ipa-cp-clone -fno-partial-inlining \ > + -fno-reorder-blocks -fno-allow-store-data-races -fasan-shadow-offset=% \ > + -Wno-packed-not-aligned -Wno-format-truncation -Wno-format-overflow \ > + -Wno-stringop-truncation -Wno-unused-but-set-variable \ > + -Wno-stringop-overflow -Wno-restrict -Wno-maybe-uninitialized \ > + -Werror=designated-init -Wno-zero-length-bounds \ > + --param=% --param asan-% > + > +# PowerPC > +bindgen_skip_c_flags += -mtraceback=no -mno-pointers-to-nested-functions \ > + -mno-string -mno-strict-align > + > +bindgen_extra_c_flags = $(TENTATIVE_CLANG_FLAGS) -Wno-address-of-packed-member > +bindgen_c_flags = $(filter-out $(bindgen_skip_c_flags), $(c_flags)) \ > + $(bindgen_extra_c_flags) > +endif > + > +bindgen_opaque_types := xregs_state desc_struct arch_lbr_state > + > +# To avoid several recompilations in PowerPC, which inserts `-D_TASK_CPU` > +bindgen_c_flags_final = $(filter-out -D_TASK_CPU=%, $(bindgen_c_flags)) > + > +quiet_cmd_bindgen = BINDGEN $@ > + cmd_bindgen = \ > + $(BINDGEN) $< $(addprefix --opaque-type , $(bindgen_opaque_types)) \ > + --use-core --with-derive-default --ctypes-prefix c_types \ > + --size_t-is-usize -o $@ -- $(bindgen_c_flags_final) -DMODULE > + > +$(objtree)/rust/bindings_generated.rs: $(srctree)/rust/kernel/bindings_helper.h FORCE > + $(call if_changed_dep,bindgen) > + > +quiet_cmd_exports = EXPORTS $@ > + cmd_exports = \ > + $(NM) -p --defined-only $< \ > + | grep -E ' (T|R|D) ' | cut -d ' ' -f 3 | grep -E '^(__rust_|_R)' \ > + | xargs -Isymbol \ > + echo 'EXPORT_SYMBOL_RUST_GPL(symbol);' > $@ > + > +$(objtree)/rust/exports_core_generated.h: $(objtree)/rust/core.o FORCE > + $(call if_changed,exports) > + > +$(objtree)/rust/exports_alloc_generated.h: $(objtree)/rust/alloc.o FORCE > + $(call if_changed,exports) > + > +$(objtree)/rust/exports_kernel_generated.h: $(objtree)/rust/kernel.o FORCE > + $(call if_changed,exports) > + > +# `-Cpanic=unwind -Cforce-unwind-tables=y` overrides `rustc_flags` in order to > +# avoid the https://github.com/rust-lang/rust/issues/82320 rustc crash. > +quiet_cmd_rustc_procmacro = $(RUSTC_OR_CLIPPY_QUIET) P $@ > + cmd_rustc_procmacro = \ > + $(RUSTC_OR_CLIPPY) $(rustc_flags) \ > + --emit=dep-info,link --extern proc_macro \ > + -Cpanic=unwind -Cforce-unwind-tables=y \ > + --crate-type proc-macro --out-dir $(objtree)/rust/ \ > + --crate-name $(patsubst lib%.so,%,$(notdir $@)) $<; \ > + mv $(objtree)/rust/$(patsubst lib%.so,%,$(notdir $@)).d $(depfile); \ > + sed -i '/^\#/d' $(depfile) > + > +$(objtree)/rust/libmodule.so: $(srctree)/rust/module.rs FORCE > + $(call if_changed_dep,rustc_procmacro) > + > +quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L $@ > + cmd_rustc_library = \ > + RUST_BINDINGS_FILE=$(abspath $(objtree)/rust/bindings_generated.rs) \ > + $(if $(skip_clippy),$(RUSTC),$(RUSTC_OR_CLIPPY)) \ > + $(rustc_flags) $(rustc_cross_flags) $(rustc_target_flags) \ > + --crate-type rlib --out-dir $(objtree)/rust/ -L $(objtree)/rust/ \ > + --crate-name $(patsubst %.o,%,$(notdir $@)) $<; \ > + mv $(objtree)/rust/$(patsubst %.o,%,$(notdir $@)).d $(depfile); \ > + sed -i '/^\#/d' $(depfile) \ > + $(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@) > + > +# `$(rustc_flags)` is passed in case the user added `--sysroot`. > +rustc_sysroot = $(shell $(RUSTC) $(rustc_flags) --print sysroot) > +rustc_src = $(rustc_sysroot)/lib/rustlib/src/rust > + > +.SECONDEXPANSION: > +$(objtree)/rust/core.o: private skip_clippy = 1 > +$(objtree)/rust/core.o: $$(rustc_src)/library/core/src/lib.rs FORCE > + $(call if_changed_dep,rustc_library) > + > +$(objtree)/rust/compiler_builtins.o: private rustc_objcopy = -w -W '__*' > +$(objtree)/rust/compiler_builtins.o: $(srctree)/rust/compiler_builtins.rs \ > + $(objtree)/rust/core.o FORCE > + $(call if_changed_dep,rustc_library) > + > +$(objtree)/rust/alloc.o: private skip_clippy = 1 > +$(objtree)/rust/alloc.o: $$(rustc_src)/library/alloc/src/lib.rs \ > + $(objtree)/rust/compiler_builtins.o FORCE > + $(call if_changed_dep,rustc_library) > + > +# ICE on `--extern module`: https://github.com/rust-lang/rust/issues/56935 > +$(objtree)/rust/kernel.o: private rustc_target_flags = --extern alloc \ > + --extern module=$(objtree)/rust/libmodule.so > +$(objtree)/rust/kernel.o: $(srctree)/rust/kernel/lib.rs $(objtree)/rust/alloc.o \ > + $(objtree)/rust/libmodule.so $(objtree)/rust/bindings_generated.rs FORCE > + $(call if_changed_dep,rustc_library) > diff --git a/scripts/Makefile.build b/scripts/Makefile.build > index 1b6094a13034..3665c49c4dcf 100644 > --- a/scripts/Makefile.build > +++ b/scripts/Makefile.build > @@ -26,6 +26,7 @@ EXTRA_CPPFLAGS := > EXTRA_LDFLAGS := > asflags-y := > ccflags-y := > +rustcflags-y := > cppflags-y := > ldflags-y := > > @@ -287,6 +288,24 @@ quiet_cmd_cc_lst_c = MKLST $@ > $(obj)/%.lst: $(src)/%.c FORCE > $(call if_changed_dep,cc_lst_c) > > +# Compile Rust sources (.rs) > +# --------------------------------------------------------------------------- > + > +rustc_cross_flags := --target=$(srctree)/arch/$(SRCARCH)/rust/target.json > + > +quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@ > + cmd_rustc_o_rs = \ > + RUST_MODFILE=$(modfile) \ > + $(RUSTC_OR_CLIPPY) $(rustc_flags) $(rustc_cross_flags) \ > + --extern alloc --extern kernel \ > + --crate-type rlib --out-dir $(obj) -L $(objtree)/rust/ \ > + --crate-name $(patsubst %.o,%,$(notdir $@)) $<; \ > + mv $(obj)/$(subst .o,,$(notdir $@)).d $(depfile); \ > + sed -i '/^\#/d' $(depfile) > + > +$(obj)/%.o: $(src)/%.rs FORCE > + $(call if_changed_dep,rustc_o_rs) > + > # Compile assembler sources (.S) > # --------------------------------------------------------------------------- > > diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib > index 8cd67b1b6d15..bd6cb3562fb4 100644 > --- a/scripts/Makefile.lib > +++ b/scripts/Makefile.lib > @@ -8,6 +8,7 @@ ldflags-y += $(EXTRA_LDFLAGS) > # flags that take effect in current and sub directories > KBUILD_AFLAGS += $(subdir-asflags-y) > KBUILD_CFLAGS += $(subdir-ccflags-y) > +KBUILD_RUSTCFLAGS += $(subdir-rustcflags-y) > > # Figure out what we need to build from the various variables > # =========================================================================== > @@ -122,6 +123,10 @@ _c_flags = $(filter-out $(CFLAGS_REMOVE_$(target-stem).o), \ > $(filter-out $(ccflags-remove-y), \ > $(KBUILD_CPPFLAGS) $(KBUILD_CFLAGS) $(ccflags-y)) \ > $(CFLAGS_$(target-stem).o)) > +_rustc_flags = $(filter-out $(RUSTCFLAGS_REMOVE_$(target-stem).o), \ > + $(filter-out $(rustcflags-remove-y), \ > + $(KBUILD_RUSTCFLAGS) $(rustcflags-y)) \ > + $(RUSTCFLAGS_$(target-stem).o)) > _a_flags = $(filter-out $(AFLAGS_REMOVE_$(target-stem).o), \ > $(filter-out $(asflags-remove-y), \ > $(KBUILD_CPPFLAGS) $(KBUILD_AFLAGS) $(asflags-y)) \ > @@ -191,6 +196,11 @@ modkern_cflags = \ > $(KBUILD_CFLAGS_MODULE) $(CFLAGS_MODULE), \ > $(KBUILD_CFLAGS_KERNEL) $(CFLAGS_KERNEL) $(modfile_flags)) > > +modkern_rustcflags = \ > + $(if $(part-of-module), \ > + $(KBUILD_RUSTCFLAGS_MODULE) $(RUSTCFLAGS_MODULE), \ > + $(KBUILD_RUSTCFLAGS_KERNEL) $(RUSTCFLAGS_KERNEL)) > + > modkern_aflags = $(if $(part-of-module), \ > $(KBUILD_AFLAGS_MODULE) $(AFLAGS_MODULE), \ > $(KBUILD_AFLAGS_KERNEL) $(AFLAGS_KERNEL)) > @@ -200,6 +210,8 @@ c_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ > $(_c_flags) $(modkern_cflags) \ > $(basename_flags) $(modname_flags) > > +rustc_flags = $(_rustc_flags) $(modkern_rustcflags) @$(objtree)/include/generated/rustc_cfg > + > a_flags = -Wp,-MMD,$(depfile) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) \ > $(_a_flags) $(modkern_aflags) > > diff --git a/scripts/kconfig/confdata.c b/scripts/kconfig/confdata.c > index 2568dbe16ed6..a83d646ecef5 100644 > --- a/scripts/kconfig/confdata.c > +++ b/scripts/kconfig/confdata.c > @@ -637,6 +637,56 @@ static struct conf_printer kconfig_printer_cb = > .print_comment = kconfig_print_comment, > }; > > +/* > + * rustc cfg printer > + * > + * This printer is used when generating the resulting rustc configuration > + * after kconfig invocation and `defconfig` files. > + */ > +static void rustc_cfg_print_symbol(FILE *fp, struct symbol *sym, const char *value, void *arg) > +{ > + const char *str; > + > + switch (sym->type) { > + case S_INT: > + case S_HEX: > + case S_BOOLEAN: > + case S_TRISTATE: > + str = sym_escape_string_value(value); > + > + /* > + * We don't care about disabled ones, i.e. no need for > + * what otherwise are "comments" in other printers. > + */ > + if (*value == 'n') > + return; > + > + /* > + * To have similar functionality to the C macro `IS_ENABLED()` > + * we provide an empty `--cfg CONFIG_X` here in both `y` > + * and `m` cases. > + * > + * Then, the common `fprintf()` below will also give us > + * a `--cfg CONFIG_X="y"` or `--cfg CONFIG_X="m"`, which can > + * be used as the equivalent of `IS_BUILTIN()`/`IS_MODULE()`. > + */ > + if (*value == 'y' || *value == 'm') > + fprintf(fp, "--cfg=%s%s\n", CONFIG_, sym->name); > + > + break; > + default: > + str = value; > + break; > + } > + > + fprintf(fp, "--cfg=%s%s=%s\n", CONFIG_, sym->name, str); > +} > + > +static struct conf_printer rustc_cfg_printer_cb = > +{ > + .print_symbol = rustc_cfg_print_symbol, > +}; > + > /* > * Header printer > * > @@ -1044,7 +1094,7 @@ int conf_write_autoconf(int overwrite) > struct symbol *sym; > const char *name; > const char *autoconf_name = conf_get_autoconfig_name(); > - FILE *out, *out_h; > + FILE *out, *out_h, *out_rustc_cfg; > int i; > > if (!overwrite && is_present(autoconf_name)) > @@ -1065,6 +1115,13 @@ int conf_write_autoconf(int overwrite) > return 1; > } > > + out_rustc_cfg = fopen(".tmp_rustc_cfg", "w"); > + if (!out_rustc_cfg) { > + fclose(out); > + fclose(out_h); > + return 1; > + } > + > conf_write_heading(out, &kconfig_printer_cb, NULL); > conf_write_heading(out_h, &header_printer_cb, NULL); > > @@ -1076,9 +1133,11 @@ int conf_write_autoconf(int overwrite) > /* write symbols to auto.conf and autoconf.h */ > conf_write_symbol(out, sym, &kconfig_printer_cb, (void *)1); > conf_write_symbol(out_h, sym, &header_printer_cb, NULL); > + conf_write_symbol(out_rustc_cfg, sym, &rustc_cfg_printer_cb, NULL); > } > fclose(out); > fclose(out_h); > + fclose(out_rustc_cfg); > > name = getenv("KCONFIG_AUTOHEADER"); > if (!name) > @@ -1097,6 +1156,12 @@ int conf_write_autoconf(int overwrite) > if (rename(".tmpconfig", autoconf_name)) > return 1; > > + name = "include/generated/rustc_cfg"; > + if (make_parent_dir(name)) > + return 1; > + if (rename(".tmp_rustc_cfg", name)) > + return 1; > + > return 0; > } > > diff --git a/scripts/rust-version.sh b/scripts/rust-version.sh > new file mode 100755 > index 000000000000..67b6d31688e2 > --- /dev/null > +++ b/scripts/rust-version.sh > @@ -0,0 +1,31 @@ > +#!/bin/sh > +# SPDX-License-Identifier: GPL-2.0 > +# > +# rust-version rust-command > +# > +# Print the compiler version of `rust-command' in a 5 or 6-digit form > +# such as `14502' for rustc-1.45.2 etc. > +# > +# Returns 0 if not found (so that Kconfig does not complain) > +compiler="$*" > + > +if [ ${#compiler} -eq 0 ]; then > + echo "Error: No compiler specified." >&2 > + printf "Usage:\n\t$0 <rust-command>\n" >&2 > + exit 1 > +fi > + > +if ! command -v $compiler >/dev/null 2>&1; then > + echo 0 > + exit 0 > +fi > + > +VERSION=$($compiler --version | cut -f2 -d' ') > + > +# Cut suffix if any (e.g. `-dev`) > +VERSION=$(echo $VERSION | cut -f1 -d'-') > + > +MAJOR=$(echo $VERSION | cut -f1 -d'.') > +MINOR=$(echo $VERSION | cut -f2 -d'.') > +PATCHLEVEL=$(echo $VERSION | cut -f3 -d'.') > +printf "%d%02d%02d\\n" $MAJOR $MINOR $PATCHLEVEL > -- > 2.17.1 > -- Thanks, ~Nick Desaulniers