はじめに
cargoにはバイナリの名前をパッケージ名と異なるものにする機能がある。
この機能はcargo-feature different_binary_name
という。
Cargo.tomlに以下を追記すると、cargo buildで出力されるバイナリはpiyo
になる。
[[bin]] name = "piyo" path = "src/piyo.rs"
この機能は以下のIssueで提案され採用された。
- [Issue] Ability to specify the output name for a bin target different from the crate name #1706
- [PR] Ability to specify the output name for a bin target different from the crate name #9627
基本事項
この記事はこのPRをもとにrust-lang/cargoのソースコードを読んでわかったことをまとめたものである。もともと自分用メモだったので、汚いのはご容赦ください、
まず、ファイル名の出力に関しての基本事項を以下に示した。これはコードを読み進めてわかったことだが、本記事ではこれらをもとにソースコードを見ていく。
Target
構造体: 全体のconfig情報を管理するOutputFile
構造体: 出力ファイルごとに作られる構造体- 出力先パスの情報
- ハードリンクに関する情報
PRで提案された手法
- Cargo.tomlからバイナリ名を取得
- バイナリ名が指定されていない => 従来通り、パッケージ名を使う。
- バイナリ名指定あり => Target構造体にそのバイナリ名を持たせる
ハードリンクに関して
cargo build
すると、バイナリはtarget/debugに作られるが、バイナリの実体はそれより下の階層(target/debug/depsなど)にある。
targetの階層につくられるバイナリは実はハードリンクである。
実際に読む
Targetはバイナリやライブラリの情報を一括してもつ。
Target.bin_name
はdifferent_binary_name
を実現するためにあるフィールド。
// src/cargo/core/manifest.rs // Information about a binary, a library, an example, etc. that is // part of the package. pub struct Target { inner: Arc<TargetInner>, } struct TargetInner { kind: TargetKind, name: String, // Note that `bin_name` is used for the cargo-feature `different_binary_name` // Some(..)ならば、[[bin]]でバイナリ名が指定されている // Noneならば、バイナリ名が指定されていない bin_name: Option<String>, // Note that the `src_path` here is excluded from the `Hash` implementation // as it's absolute currently and is otherwise a little too brittle for // causing rebuilds. Instead the hash for the path that we send to the // compiler is handled elsewhere. src_path: TargetSourcePath, required_features: Option<Vec<String>>, tested: bool, benched: bool, doc: bool, doctest: bool, harness: bool, // whether to use the test harness (--test) for_host: bool, proc_macro: bool, edition: Edition, } pub enum TargetKind { Lib(Vec<CrateType>), Bin, Test, Bench, ExampleLib(Vec<CrateType>), ExampleBin, CustomBuild, } pub fn bin_target( name: &str, bin_name: Option<String>, src_path: PathBuf, required_features: Option<Vec<String>>, edition: Edition, ) -> Target { ... } impl Target { ... // バイナリの名前を返すメソッド pub fn binary_filename(&self) -> Option<String> { self.inner.bin_name.clone() } // バイナリの名前をセットするメソッド pub fn set_binary_name(&mut self, bin_name: Option<String>) -> &mut Target { Arc::make_mut(&mut self.inner).bin_name = bin_name; self } ... }
以下は関連するコード tomlで渡されたfilenameをTarget構造体に渡す。
// src/cargo/util/toml/targets.rs fn clean_bins( features: &Features, toml_bins: Option<&Vec<TomlBinTarget>>, package_root: &Path, package_name: &str, edition: Edition, autodiscover: Option<bool>, warnings: &mut Vec<String>, errors: &mut Vec<String>, has_lib: bool, ) -> CargoResult<Vec<Target>> { ... let mut target = Target::bin_target( &bin.name(), bin.filename.clone(), path, bin.required_features.clone(), edition, ); ... }
出力するバイナリの名前を決定する関数。 重要なのはSomeとNoneの場合分け。
// src/cargo/core/compiler/build_context/target_info.rs /// The filename for this FileType that Cargo should use when "uplifting" /// it to the destination directory. pub fn uplift_filename(&self, target: &Target) -> String { let name = match target.binary_filename() { // [[bin]]でバイナリの名前が指定されているのでそれを使う Some(name) => name, // パッケージ名をバイナリの名前に使う None => { // For binary crate type, `should_replace_hyphens` will always be false. // 名前に含まれるハイフンを処理する if self.should_replace_hyphens { target.crate_name() } else { target.name().to_string() } } }; format!("{}{}{}", self.prefix, name, self.suffix) }
次にハードリンクについて。 本PRでコードの修正は入ってないが、参考のために載せておく。
uplift_to関数: Option<PathBuff>
型を返す
戻り値はSome(path)なら、pathにハードリンクを作る、Noneなら、ハードリンクを作らないことを意味する。
calc_outputs_rustc関数: CargoResult<Vec<OutputFile>>
型を返す。
戻り値はrustcが必要とする出力ファイルの情報をもつ。
// src/cargo/core/compiler/context/compilation_files.rs impl<'a, 'cfg: 'a> CompilationFiles<'a, 'cfg> { ... /// Returns the path where the output for the given unit and FileType /// should be uplifted to. /// /// Returns `None` if the unit shouldn't be uplifted (for example, a /// dependent rlib). fn uplift_to(&self, unit: &Unit, file_type: &FileType, from_path: &Path) -> Option<PathBuf> { ... } // rustcによって作成されたファイル情報を出力する /// Computes the actual, full pathnames for all the files generated by rustc. /// /// The `OutputFile` also contains the paths where those files should be /// "uplifted" to. fn calc_outputs_rustc( &self, unit: &Unit, bcx: &BuildContext<'a, 'cfg>, ) -> CargoResult<Vec<OutputFile>> { ... // Convert FileType to OutputFile. let mut outputs = Vec::new(); for file_type in file_types { let meta = &self.metas[unit]; let meta_opt = meta.use_extra_filename.then(|| meta.meta_hash.to_string()); let path = out_dir.join(file_type.output_filename(&unit.target, meta_opt.as_deref())); // ハードリンクで参照する名前を取得 // If, the `different_binary_name` feature is enabled, the name of the hardlink will // be the name of the binary provided by the user in `Cargo.toml`. let hardlink = self.uplift_to(unit, &file_type, &path); let export_path = if unit.target.is_custom_build() { None } else { self.export_dir.as_ref().and_then(|export_dir| { hardlink .as_ref() .map(|hardlink| export_dir.join(hardlink.file_name().unwrap())) }) }; outputs.push(OutputFile { path, hardlink, export_path, flavor: file_type.flavor, }); } Ok(outputs) } ... }
cargoが最終的に生成するOutputFile
構造体はこんな感じで定義されている。
// src/cargo/core/compiler/context/compilation_files.rs pub struct OutputFile { /// Absolute path to the file that will be produced by the build process. pub path: PathBuf, /// If it should be linked into `target`, and what it should be called /// (e.g., without metadata). pub hardlink: Option<PathBuf>, /// If `--out-dir` is specified, the absolute path to the exported file. pub export_path: Option<PathBuf>, /// Type of the file (library / debug symbol / else). pub flavor: FileFlavor, }
感想
PRを読むのは勉強になる。 インクリメンタルコンパイルのためのfingerprintという機能があるらしいが、それがどのようなものか、どのようにcargoで処理されているか気になった。
Ref
- cargo doc (https://doc.rust-lang.org/cargo/reference/manifest.html)
- changed files (https://github.com/rust-lang/cargo/pull/9627/files#)