@@ -90,6 +90,12 @@ pub enum ParsedCli { |
| 90 | 90 | Info(InfoAction), |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 94 | +enum CliInputKind { |
| 95 | + FortranSource, |
| 96 | + LinkArtifact, |
| 97 | +} |
| 98 | + |
| 93 | 99 | /// Compilation options. |
| 94 | 100 | pub struct Options { |
| 95 | 101 | // ---- I/O ---- |
@@ -831,6 +837,82 @@ fn main_wrapper_target(allocated: &[MachineFunction]) -> Option<&str> { |
| 831 | 837 | .map(|func| func.name.as_str()) |
| 832 | 838 | } |
| 833 | 839 | |
| 840 | +fn all_input_paths(opts: &Options) -> Vec<PathBuf> { |
| 841 | + let mut inputs = vec![opts.input.clone()]; |
| 842 | + inputs.extend(opts.extra_inputs.iter().cloned()); |
| 843 | + inputs |
| 844 | +} |
| 845 | + |
| 846 | +fn classify_cli_input(path: &Path) -> CliInputKind { |
| 847 | + let ext = path |
| 848 | + .extension() |
| 849 | + .and_then(|ext| ext.to_str()) |
| 850 | + .map(|ext| ext.to_ascii_lowercase()); |
| 851 | + match ext.as_deref() { |
| 852 | + Some("o" | "obj" | "a" | "dylib" | "so") => CliInputKind::LinkArtifact, |
| 853 | + _ => CliInputKind::FortranSource, |
| 854 | + } |
| 855 | +} |
| 856 | + |
| 857 | +fn validate_link_only_inputs(opts: &Options) -> Result<(), String> { |
| 858 | + if opts.preprocess_only { |
| 859 | + return Err("-E cannot be used when all inputs are prebuilt objects or archives".into()); |
| 860 | + } |
| 861 | + if opts.emit_tokens { |
| 862 | + return Err( |
| 863 | + "--emit-tokens cannot be used when all inputs are prebuilt objects or archives".into(), |
| 864 | + ); |
| 865 | + } |
| 866 | + if opts.emit_ast { |
| 867 | + return Err( |
| 868 | + "--emit-ast cannot be used when all inputs are prebuilt objects or archives".into(), |
| 869 | + ); |
| 870 | + } |
| 871 | + if opts.emit_ir { |
| 872 | + return Err( |
| 873 | + "--emit-ir cannot be used when all inputs are prebuilt objects or archives".into(), |
| 874 | + ); |
| 875 | + } |
| 876 | + if opts.emit_asm { |
| 877 | + return Err("-S cannot be used when all inputs are prebuilt objects or archives".into()); |
| 878 | + } |
| 879 | + if opts.emit_obj { |
| 880 | + return Err("-c cannot be used when all inputs are prebuilt objects or archives".into()); |
| 881 | + } |
| 882 | + Ok(()) |
| 883 | +} |
| 884 | + |
| 885 | +/// Execute a fully parsed CLI job, dispatching between source |
| 886 | +/// compilation and pure link steps based on the positional inputs. |
| 887 | +pub fn execute(opts: &Options) -> Result<(), String> { |
| 888 | + let inputs = all_input_paths(opts); |
| 889 | + let has_source = inputs |
| 890 | + .iter() |
| 891 | + .any(|path| classify_cli_input(path) == CliInputKind::FortranSource); |
| 892 | + let has_link_artifact = inputs |
| 893 | + .iter() |
| 894 | + .any(|path| classify_cli_input(path) == CliInputKind::LinkArtifact); |
| 895 | + |
| 896 | + match (has_source, has_link_artifact) { |
| 897 | + (true, false) => { |
| 898 | + if opts.extra_inputs.is_empty() { |
| 899 | + compile(opts) |
| 900 | + } else { |
| 901 | + compile_multi(opts) |
| 902 | + } |
| 903 | + } |
| 904 | + (false, true) => { |
| 905 | + validate_link_only_inputs(opts)?; |
| 906 | + let output = opts.output.clone().unwrap_or_else(|| PathBuf::from("a.out")); |
| 907 | + link_inputs(&inputs, &output, opts) |
| 908 | + } |
| 909 | + (true, true) => Err( |
| 910 | + "mixing Fortran sources with prebuilt object/archive inputs is not yet supported; compile the sources first and then link the resulting objects".into(), |
| 911 | + ), |
| 912 | + (false, false) => unreachable!("parse_cli guarantees at least one input"), |
| 913 | + } |
| 914 | +} |
| 915 | + |
| 834 | 916 | /// Compile a Fortran source file through the full pipeline. |
| 835 | 917 | pub fn compile(opts: &Options) -> Result<(), String> { |
| 836 | 918 | let mut phases = PhaseTimer::new(opts.time_report); |
@@ -1299,6 +1381,12 @@ _main: |
| 1299 | 1381 | /// `opts` contributes the user-supplied `-L`, `-l`, `-rpath`, |
| 1300 | 1382 | /// `-shared`, and `-static` flags that need to make it through to ld. |
| 1301 | 1383 | fn link(obj: &Path, output: &Path, opts: &Options) -> Result<(), String> { |
| 1384 | + link_inputs(&[obj.to_path_buf()], output, opts) |
| 1385 | +} |
| 1386 | + |
| 1387 | +/// Link prebuilt objects and archives with the runtime to produce a |
| 1388 | +/// binary or shared library, preserving the user-supplied input order. |
| 1389 | +fn link_inputs(inputs: &[PathBuf], output: &Path, opts: &Options) -> Result<(), String> { |
| 1302 | 1390 | let rt_path = find_runtime_lib()?; |
| 1303 | 1391 | let sdk = Command::new("xcrun") |
| 1304 | 1392 | .args(["--show-sdk-path"]) |
@@ -1306,8 +1394,11 @@ fn link(obj: &Path, output: &Path, opts: &Options) -> Result<(), String> { |
| 1306 | 1394 | .map_err(|e| format!("cannot run xcrun: {}", e))?; |
| 1307 | 1395 | let sysroot = String::from_utf8_lossy(&sdk.stdout).trim().to_string(); |
| 1308 | 1396 | |
| 1309 | | - let mut args: Vec<String> = vec![ |
| 1310 | | - obj.to_string_lossy().into_owned(), |
| 1397 | + let mut args: Vec<String> = vec!["-o".into(), output.to_string_lossy().into_owned()]; |
| 1398 | + for input in inputs { |
| 1399 | + args.push(input.to_string_lossy().into_owned()); |
| 1400 | + } |
| 1401 | + args.extend([ |
| 1311 | 1402 | rt_path, |
| 1312 | 1403 | "-lSystem".into(), |
| 1313 | 1404 | "-no_uuid".into(), |
@@ -1315,9 +1406,7 @@ fn link(obj: &Path, output: &Path, opts: &Options) -> Result<(), String> { |
| 1315 | 1406 | sysroot, |
| 1316 | 1407 | "-e".into(), |
| 1317 | 1408 | "_main".into(), |
| 1318 | | - "-o".into(), |
| 1319 | | - output.to_string_lossy().into_owned(), |
| 1320 | | - ]; |
| 1409 | + ]); |
| 1321 | 1410 | push_link_flags(&mut args, opts); |
| 1322 | 1411 | |
| 1323 | 1412 | let ld_result = Command::new("ld") |
@@ -1361,38 +1450,7 @@ fn push_link_flags(args: &mut Vec<String>, opts: &Options) { |
| 1361 | 1450 | |
| 1362 | 1451 | /// Link multiple object files with the runtime to produce a binary. |
| 1363 | 1452 | fn link_multi(objs: &[PathBuf], output: &Path, opts: &Options) -> Result<(), String> { |
| 1364 | | - let rt_path = find_runtime_lib()?; |
| 1365 | | - let sdk = Command::new("xcrun") |
| 1366 | | - .args(["--show-sdk-path"]) |
| 1367 | | - .output() |
| 1368 | | - .map_err(|e| format!("cannot run xcrun: {}", e))?; |
| 1369 | | - let sysroot = String::from_utf8_lossy(&sdk.stdout).trim().to_string(); |
| 1370 | | - |
| 1371 | | - let mut args: Vec<String> = vec![ |
| 1372 | | - "-o".into(), output.to_str().unwrap().into(), |
| 1373 | | - ]; |
| 1374 | | - for obj in objs { |
| 1375 | | - args.push(obj.to_str().unwrap().into()); |
| 1376 | | - } |
| 1377 | | - args.extend([ |
| 1378 | | - rt_path, |
| 1379 | | - "-lSystem".into(), |
| 1380 | | - "-no_uuid".into(), |
| 1381 | | - "-syslibroot".into(), |
| 1382 | | - sysroot, |
| 1383 | | - "-e".into(), |
| 1384 | | - "_main".into(), |
| 1385 | | - ]); |
| 1386 | | - push_link_flags(&mut args, opts); |
| 1387 | | - let ld_result = Command::new("ld") |
| 1388 | | - .args(&args) |
| 1389 | | - .output() |
| 1390 | | - .map_err(|e| format!("cannot run linker: {}", e))?; |
| 1391 | | - if !ld_result.status.success() { |
| 1392 | | - let stderr = String::from_utf8_lossy(&ld_result.stderr); |
| 1393 | | - return Err(format!("linker failed:\n{}", stderr)); |
| 1394 | | - } |
| 1395 | | - Ok(()) |
| 1453 | + link_inputs(objs, output, opts) |
| 1396 | 1454 | } |
| 1397 | 1455 | |
| 1398 | 1456 | /// Compile multiple Fortran source files with automatic dependency |