本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
转载自夜明的孤行灯
本文链接地址: https://www.huangyunkun.com/2020/06/13/rust-for-switch-homebrew/
写Switch应用首选肯定是C/C++语言,主要依赖是devkitpro和libnx。
如果要用Rust语言来写,最核心的就是两个问题,一个是和核心的libnx交互,一个编译产出的配置。
Rust FFI
由于现实中很多程序是由不同编程语言写的,必然会涉及到跨语言调用,比如 A 语言写的函数如果想在 B 语言里面调用,这时一般有两种解决方案:一种是将函数做成一个服务,通过进程间通信或网络协议通信;另一种就是直接通过 FFI 调用。前者需要至少两个独立的进程才能实现,而后者直接将其它语言的接口内嵌到本语言中,所以调用效率比前者高。
Rust 本身定位为系统编程语言,不可避免要和现有的 C/C++ 代码交互。
Rust FFI原理和使用文档不少,这里要解决了的主要是简便。因为手写所有绑定是不现实的。
rust-bindgen可以帮助我们生成对应的绑定文件,支持C和部分C++。这里我们先只考虑核心的libnx。
创建build.rs文件
extern crate bindgen;
use std::env;
use std::path::PathBuf;
pub fn main() {
let devkitpro_path = env::var("DEVKITPRO").unwrap();
println!("cargo:rustc-link-lib=static=nx");
println!(
"cargo:rustc-link-search=native={}/libnx/lib",
devkitpro_path
);
// The bindgen::Builder is the main entry point
// to bindgen, and lets you build up options for
// the resulting bindings.
let bindings = bindgen::Builder::default()
// The input header we would like to generate bindings for.
.trust_clang_mangling(false)
.use_core()
.ctypes_prefix("lang_items")
.header("wrapper.h")
.clang_arg(format!("-I{}/libnx/include", devkitpro_path))
.clang_arg(format!(
"-I{}/devkitA64/aarch64-none-elf/include",
devkitpro_path
))
.bitfield_enum("HidMouseButton")
.bitfield_enum("HidKeyboardModifier")
.rustified_enum("HidKeyboardScancode")
.bitfield_enum("HidControllerType")
.rustified_enum("HidControllerLayoutType")
.bitfield_enum("HidControllerColorDescription")
.bitfield_enum("HidControllerKeys")
.rustified_enum("HidControllerJoystick")
.bitfield_enum("HidControllerConnectionState")
.rustified_enum("HidControllerID")
.generate_inline_functions(true)
.blacklist_type("u8")
.blacklist_type("u16")
.blacklist_type("u32")
.blacklist_type("u64")
.blacklist_type("u128")
.layout_tests(false)
// Finish the builder and generate the bindings.
.generate()
// Unwrap the Result and panic on failure.
.expect("Unable to generate bindings");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from("src");
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
核心配置就是链接nx库,并指定搜索地址,另外就是include的搜索地址,以后要加新的外部库也需要新增。生成的位置我放在src目录的,主要是为了智能感应,这个文件需要gitignore掉,很多配置是输出到out目录的,本质上没有区别。
新建wrapper.h文件
#include <switch.h>
#include <stdio.h>
在rust代码中直接引用即可
include!("bindings.rs");
构建配置
构建的配置大同小异主要是两点,一是交叉编译,另外一个是switch app本身的配置
交叉变异使用cargo-xbuild,target配置如下
{
"abi-blacklist": [
"stdcall",
"fastcall",
"vectorcall",
"thiscall",
"win64",
"sysv64"
],
"arch": "aarch64",
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
"executables": true,
"linker": "aarch64-none-elf-ld",
"linker-flavor": "ld",
"linker-is-gnu": true,
"llvm-target": "aarch64-unknown-none",
"no-compiler-rt": true,
"features": "+a53,+strict-align",
"max-atomic-width": 128,
"os": "none",
"panic": "abort",
"panic-strategy": "abort",
"position-independent-executables": true,
"relocation-model": "pic",
"target-c-int-width": "32",
"target-endian": "little",
"target-pointer-width": "64",
"disable-redzone": true
}
整个Makefile配置如下,其中部分是应用相关的配置,注意修改。icon是jpg格式,256*256像素。
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
#
# NO_ICON: if set to anything, do not use icon.
# NO_NACP: if set to anything, no .nacp file is generated.
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
# ICON is the filename of the icon (.jpg), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <Project name>.jpg
# - icon.jpg
# - <libnx folder>/default_icon.jpg
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
OUTDIR := out
BUILD := build
SOURCES := src
DATA := data
INCLUDES := include
EXEFS_SRC := exefs_src
APP_TITLE := Switch Backup
APP_AUTHOR := htynkn
APP_VERSION := 0.0.1
#---------------------------------------------------------------------------------
# rust variables
#---------------------------------------------------------------------------------
TARGET_TRIPLE ?= aarch64-none-elf
XARGO ?= RUST_TARGET_PATH="$(TOPDIR)" cargo xbuild
CARGO ?= RUST_TARGET_PATH="$(TOPDIR)" cargo
RUST_BINARY := $(shell cat $(TOPDIR)/Cargo.toml | grep name | cut -d\" -f 2 | tr - _)
RUST_BUILD_DIR := $(TOPDIR)/target/$(TARGET_TRIPLE)
RUST_RELEASE_LIB := $(RUST_BUILD_DIR)/release/lib$(RUST_BINARY).a
RUST_DEPS = $(TOPDIR)/Cargo.toml $(TOPDIR)/build.rs $(TOPDIR)/src/*.rs
RUST_LIB := $(TOPDIR)/$(BUILD)/lib$(RUST_BINARY).a
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -DSWITCH
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lnx -L. -l$(RUST_BINARY)
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(LIBNX)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(OUTDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.jpg)
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
else
ifneq (,$(findstring icon.jpg,$(icons)))
export APP_ICON := $(TOPDIR)/icon.jpg
endif
endif
else
export APP_ICON := $(TOPDIR)/$(ICON)
endif
ifeq ($(strip $(NO_ICON)),)
export NROFLAGS += --icon=$(APP_ICON)
endif
ifeq ($(strip $(NO_NACP)),)
export NROFLAGS += --nacp=$(CURDIR)/$(OUTDIR)/$(TARGET).nacp
endif
ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(RUST_RELEASE_LIB) $(BUILD)
$(BUILD):
mkdir -p $@ $(BUILD) $(OUTDIR)
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@$(CARGO) clean
@rm -fr $(OUTDIR) $(BUILD)/* target src/bindings.rs
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).pfs0 $(OUTPUT).nro
$(OUTPUT).pfs0 : $(OUTPUT).nso
$(OUTPUT).nso : $(OUTPUT).elf
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
else
$(OUTPUT).nro : $(OUTPUT).elf
endif
$(OUTPUT).elf : $(OFILES) $(RUST_LIB)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------
#---------------------------------------------------------------------------------
# rust rules
#---------------------------------------------------------------------------------
check:
@$(XARGO) check --target=$(TARGET_TRIPLE)
$(RUST_RELEASE_LIB): $(RUST_DEPS)
@echo "+ Building $@ [cargo build --release] (via shell out)"
@echo "+ Command: $(XARGO) --release --target=$(TARGET_TRIPLE)"
#@$(XARGO) build --release --target=$(TARGET_TRIPLE)
@$(shell bash -c '$(XARGO) --release --target=$(TARGET_TRIPLE)')
$(RUST_LIB): $(RUST_RELEASE_LIB) | $(BUILD_DIR)
@cp $< $@
构建效果

CI构建
涉及到这种要交叉编译的东西依赖环境都比较麻烦,要安装llvm等,这里我封装了一个docker镜像可以直接使用,安装了核心依赖外还安装了目前所有可用的portlibs。
使用的时候直接
docker run -v $(pwd):/Switch-Backup -w /Switch-Backup htynkn/switch-devenv bash -c "make"
其中的目录改成你需要的即可。
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
转载自夜明的孤行灯
本文链接地址: https://www.huangyunkun.com/2020/06/13/rust-for-switch-homebrew/
老哥有demo吗?按教程操作下来一顿报错