diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e9fba92..aad7ff5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -209,9 +209,21 @@ jobs: - run: cargo build - run: ./target/debug/catapult generate --source-dir test_data/test_xcode_basic --build-dir build --generator Xcode --toolchain test_data/toolchain_xcode.toml - run: ls -R build - - run: xcodebuild -project build/basic/basic.xcodeproj -configuration Debug -target basic.exe SYMROOT=$PWD/build/basic/DerivedData build + - run: > + xcodebuild + -project build/basic/basic.xcodeproj + -configuration Debug + -scheme basic.exe + -derivedDataPath "$PWD/build/basic/DerivedData" + build - run: ls -R build - - run: ./build/basic/DerivedData/Debug/basic.exe - - run: xcodebuild -project build/basic/basic.xcodeproj -configuration Release -target basic.exe SYMROOT=$PWD/build/basic/DerivedData build + - run: ./build/basic/DerivedData/Build/Products/Debug/basic.exe + - run: > + xcodebuild + -project build/basic/basic.xcodeproj + -configuration Release + -scheme basic.exe + -derivedDataPath "$PWD/build/basic/DerivedData" + build - run: ls -R build - - run: ./build/basic/DerivedData/Release/basic.exe + - run: ./build/basic/DerivedData/Build/Products/Release/basic.exe diff --git a/src/generator.rs b/src/generator.rs index e1a1b1e..6af6499 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -19,6 +19,12 @@ pub enum Generator { Xcode, } +enum Os { + Darwin, + Linux, + Windows, +} + impl Generator { pub fn generate( &self, @@ -38,19 +44,8 @@ impl Generator { } else { String::new() }; - let target_platform = if target_triple.contains("-windows-") || target_triple.ends_with("-windows") { - TargetPlatform { - obj_ext: ".obj".to_owned(), - static_lib_ext: ".lib".to_owned(), - exe_ext: ".exe".to_owned(), - } - } else { - TargetPlatform { - obj_ext: ".o".to_owned(), - static_lib_ext: ".a".to_owned(), - exe_ext: "".to_owned(), - } - }; + log::info!("target_triple: {}", target_triple); + let target_platform = TargetPlatform::from_target_triple(&target_triple); ninja::Ninja::generate(project, build_dir, toolchain, profile, global_opts, target_platform) } Generator::Xcode => xcode::Xcode::generate(project, build_dir, toolchain, global_opts), @@ -59,7 +54,58 @@ impl Generator { } pub struct TargetPlatform { + os: Os, pub obj_ext: String, pub static_lib_ext: String, + pub shared_lib_ext: String, + pub shared_link_ext: String, pub exe_ext: String, } + +impl TargetPlatform { + pub fn from_target_triple(target_triple: &str) -> Self { + let target_platform = if target_triple.contains("-windows-") || target_triple.ends_with("-windows") { + TargetPlatform { + os: Os::Windows, + obj_ext: ".obj".to_owned(), + static_lib_ext: ".lib".to_owned(), + shared_lib_ext: ".dll".to_owned(), + shared_link_ext: ".lib".to_owned(), + exe_ext: ".exe".to_owned(), + } + } else if target_triple.contains("-darwin-") || target_triple.ends_with("-darwin") { + TargetPlatform { + os: Os::Darwin, + obj_ext: ".o".to_owned(), + static_lib_ext: ".a".to_owned(), + shared_lib_ext: ".dylib".to_owned(), + shared_link_ext: ".dylib".to_owned(), + exe_ext: "".to_owned(), + } + } else { + TargetPlatform { + os: Os::Linux, + obj_ext: ".o".to_owned(), + static_lib_ext: ".a".to_owned(), + shared_lib_ext: ".so".to_owned(), + shared_link_ext: ".so".to_owned(), + exe_ext: "".to_owned(), + } + }; + target_platform + } + pub fn shared_runtime_identity_flags(&self, lib_name: &str) -> Option { + match self.os { + Os::Darwin => Some(format!("-Wl,-install_name,@rpath/{lib_name}.dylib")), + Os::Linux => Some(format!("-Wl,-soname,{lib_name}.so")), + Os::Windows => None, + } + } + + pub fn runtime_search_path_flags(&self, paths: &[String]) -> Vec { + match self.os { + Os::Windows => Vec::new(), + Os::Darwin | Os::Linux => paths.iter().map(|path| format!("-Wl,-rpath,{path}")).collect(), + } + } +} diff --git a/src/generator/msvc.rs b/src/generator/msvc.rs index 83f310b..9c53f64 100644 --- a/src/generator/msvc.rs +++ b/src/generator/msvc.rs @@ -15,6 +15,7 @@ use crate::{ misc::{Sources, index_map, join_parent}, object_library::ObjectLibrary, project::{Project, ProjectInfo}, + shared_library::SharedLibrary, starlark_context::{StarContext, StarContextCompiler}, starlark_generator::eval_vars, starlark_object_library::StarGeneratorVars, @@ -120,6 +121,11 @@ struct ProfileFragment { nasm_assemble_flags: Vec, } +struct DynamicImportSettings { + import_library: Option, + ignore_import_library: Option, +} + fn item_definition_group( platform: &str, profile_name: &str, @@ -128,6 +134,7 @@ fn item_definition_group( include_dirs: &[String], defines: &[String], opts: &Options, + dynamic_import_settings: Option<&DynamicImportSettings>, ) -> Result { let mut ret = format!( r#" @@ -140,11 +147,26 @@ fn item_definition_group( if !sources.nasm.is_empty() { ret += &nasm_compile(profile, platform, include_dirs, defines)?; } - if !profile.vcxproj.link.is_empty() { + let has_dynamic_import_settings = match dynamic_import_settings { + Some(settings) => settings.import_library.is_some() || settings.ignore_import_library.is_some(), + None => false, + }; + if !profile.vcxproj.link.is_empty() || has_dynamic_import_settings { ret += " \n"; for (key, val) in &profile.vcxproj.link { ret += &format!(" <{key}>{val}\n") } + if let Some(import_settings) = dynamic_import_settings { + if let Some(import_library) = &import_settings.import_library { + ret += &format!(" {import_library}\n"); + } + if let Some(ignore_import_library) = import_settings.ignore_import_library { + ret += &format!( + " {}\n", + if ignore_import_library { "true" } else { "false" } + ); + } + } ret += " \n"; } ret += " \n"; @@ -162,7 +184,16 @@ fn item_group_conditional(sources: &Sources, project_info: &ProjectInfo, platfor ret += &item_group_tag; for src in &sources.c { let input = input_path(&src.full, &project_info.path); - ret += &format!(" \n"); + if src.full.ends_with(".c") { + ret += &format!(" \n"); + } else { + ret += &format!( + r#" + CompileAsC + +"# + ); + } } ret += " \n"; } @@ -170,7 +201,16 @@ fn item_group_conditional(sources: &Sources, project_info: &ProjectInfo, platfor ret += &item_group_tag; for src in &sources.cpp { let input = input_path(&src.full, &project_info.path); - ret += &format!(" \n"); + if src.full.ends_with(".cpp") { + ret += &format!(" \n"); + } else { + ret += &format!( + r#" + CompileAsCpp + +"# + ); + } } ret += " \n"; } @@ -277,8 +317,9 @@ struct TargetData { sources: Sources, includes: Vec, defines: Vec, - links: Vec, + direct_links: Vec, generator_vars: Option, + output_name: Option, } struct VcxprojOpts { @@ -437,6 +478,11 @@ impl Msvc { add_object_lib(lib, proj_opts, targets)?; } } + for lib in &project.shared_libraries { + if !targets.contains_key(&as_key(lib)) { + add_shared_lib(lib, proj_opts, targets)?; + } + } for exe in &project.executables { let configuration_type = "Application"; let project_info = &exe.project().info; @@ -450,8 +496,9 @@ impl Msvc { .map(|x| x.to_string_lossy().trim_start_matches(r"\\?\").to_owned()) .collect::>(), defines: exe.internal_defines(), - links: exe.links.clone(), + direct_links: exe.links.clone(), generator_vars: exe.generator_vars.clone(), + output_name: exe.output_name.clone(), }; let vsproj = make_vcxproj(proj_opts, targets, configuration_type, project_info, &target_data)?; targets.insert(as_key(exe), vsproj); @@ -474,7 +521,7 @@ fn add_static_lib( .map(|x| x.to_string_lossy().trim_start_matches(r"\\?\").to_owned()) .collect::>(); let defines = lib.internal_defines(); - let links = lib + let direct_links = lib .link_private .iter() .cloned() @@ -485,8 +532,9 @@ fn add_static_lib( sources: lib.sources.clone(), includes, defines, - links, + direct_links, generator_vars: lib.generator_vars.clone(), + output_name: lib.output_name.clone(), }; let vsproj = make_vcxproj(proj_opts, targets, "StaticLibrary", project_info, &target_data)?; targets.insert(as_key(lib), vsproj.clone()); @@ -507,7 +555,7 @@ fn add_object_lib<'a>( .map(|x| x.to_string_lossy().trim_start_matches(r"\\?\").to_owned()) .collect::>(); let defines = lib.internal_defines(); - let links = lib + let direct_links = lib .link_private .iter() .cloned() @@ -518,14 +566,48 @@ fn add_object_lib<'a>( sources: lib.sources.clone(), includes, defines, - links, + direct_links, generator_vars: lib.generator_vars.clone(), + output_name: lib.output_name.clone(), }; let vsproj = make_vcxproj(proj_opts, targets, "StaticLibrary", project_info, &target_data)?; targets.insert(as_key(lib), vsproj.clone()); Ok(vsproj) } +fn add_shared_lib( + lib: &Arc, + proj_opts: &VcxprojOpts, + targets: &mut TargetProjects, +) -> Result { + log::debug!("add_shared_lib: {}", lib.name); + let project_info = &lib.project().info; + let includes = lib + .internal_includes() + .into_iter() + .map(|x| x.to_string_lossy().trim_start_matches(r"\\?\").to_owned()) + .collect::>(); + let defines = lib.internal_defines(); + let direct_links = lib + .link_private + .iter() + .cloned() + .chain(lib.link_public.iter().cloned()) + .collect(); + let target_data = TargetData { + name: lib.name.clone(), + sources: lib.sources.clone(), + includes, + defines, + direct_links, + generator_vars: lib.generator_vars.clone(), + output_name: lib.output_name.clone(), + }; + let vsproj = make_vcxproj(proj_opts, targets, "DynamicLibrary", project_info, &target_data)?; + targets.insert(as_key(lib), vsproj.clone()); + Ok(vsproj) +} + fn make_vcxproj( proj_opts: &VcxprojOpts, targets: &mut TargetProjects, @@ -576,6 +658,9 @@ fn make_vcxproj( {PLATFORM_TOOLSET} "# ); + if let Some(out) = &target_data.output_name { + out_str += &format!(" {out}\n"); + } // true // MultiByte // true @@ -624,6 +709,22 @@ fn make_vcxproj( .chain(generator_vars.defines.clone()) .collect::>(); for (profile_name, profile) in &proj_opts.profiles { + let dynamic_import_settings = if configuration_type != "DynamicLibrary" { + None + } else { + Some(DynamicImportSettings { + import_library: if !profile.vcxproj.link.contains_key("ImportLibrary") { + Some("$(OutDir)$(TargetName).lib".to_owned()) + } else { + None + }, + ignore_import_library: if !profile.vcxproj.link.contains_key("IgnoreImportLibrary") { + Some(false) + } else { + None + }, + }) + }; item_definition_groups.push(item_definition_group( platform, profile_name, @@ -632,6 +733,7 @@ fn make_vcxproj( &includes_gen, &defines_gen, &proj_opts.opts, + dynamic_import_settings.as_ref(), )?); } item_groups.push(item_group_conditional(&generator_sources, project_info, platform)); @@ -694,9 +796,9 @@ fn make_vcxproj( } let mut dependencies = Vec::new(); - if !target_data.links.is_empty() { + if !target_data.direct_links.is_empty() { out_str += " \n"; - out_str += &add_project_references(&target_data.links, proj_opts, targets, &mut dependencies)?; + out_str += &add_project_references(&target_data.direct_links, proj_opts, targets, &mut dependencies)?; out_str += " \n"; } out_str += r#" @@ -780,6 +882,16 @@ fn add_project_references( LinkPtr::Interface(_) => { out_str += &add_project_references(&link.public_links(), proj_opts, targets, dependencies)?; } + LinkPtr::Shared(shared_lib) => { + let proj_ref = match targets.get(&as_key(shared_lib)) { + Some(x) => x, + None => { + add_shared_lib(shared_lib, proj_opts, targets)?; + targets.get(&as_key(shared_lib)).unwrap() + } + }; + add_dependency(proj_ref); + } } } Ok(out_str) diff --git a/src/generator/ninja.rs b/src/generator/ninja.rs index 8e7cfda..72b560b 100644 --- a/src/generator/ninja.rs +++ b/src/generator/ninja.rs @@ -14,9 +14,14 @@ use crate::{ GlobalOptions, executable::Executable, link_type::LinkPtr, - misc::{Sources, join_parent}, + misc::{ + Sources, + index_set::IndexSet, // + join_parent, + }, object_library::ObjectLibrary, project::Project, + shared_library::SharedLibrary, starlark_context::{StarContext, StarContextCompiler}, starlark_generator::eval_vars, starlark_object_library::StarGeneratorVars, @@ -166,6 +171,7 @@ struct NinjaRules { compile_cpp_object: Option, assemble_nasm_object: Option, link_static_lib: Option, + link_shared: Option, link_exe: Option, } @@ -251,6 +257,22 @@ fn link_static_lib(static_linker: &[String]) -> NinjaRule { ..Default::default() } } +fn link_shared(shared_linker: &[String]) -> NinjaRule { + let mut command = shared_linker.to_owned(); + command.extend(vec![ + "$LINK_FLAGS".to_string(), + "$in".to_string(), + "-o".to_string(), + "$TARGET_FILE".to_string(), + "$LINK_PATH".to_string(), + ]); + NinjaRule { + name: String::from("link_shared"), + command, + description: Some("Linking shared lib $out".to_owned()), + ..Default::default() + } +} fn link_exe(exe_linker: &dyn ExeLinker) -> NinjaRule { let mut command = exe_linker.cmd(); command.extend(vec![ @@ -328,6 +350,9 @@ impl Ninja { if let Some(c) = rules.link_static_lib { rules_str += &c.as_string(); } + if let Some(c) = rules.link_shared { + rules_str += &c.as_string(); + } if let Some(c) = rules.link_exe { rules_str += &c.as_string(); } @@ -377,6 +402,10 @@ impl Ninja { link_targets.entry(key).or_default(); } + for lib in &project.shared_libraries { + add_shared_lib_target(lib, generator_opts, rules, build_lines, link_targets)?; + } + for exe in &project.executables { add_executable_target(exe, generator_opts, rules, build_lines, link_targets)?; } @@ -501,6 +530,17 @@ fn add_object_lib_target( inputs.push(link_path); } } + LinkPtr::Shared(_) => { + let link_path = output_path( + build_dir, + &link.project().info.name, + link.output_name(), + &target_platform.shared_link_ext, + ); + if !inputs.contains(&link_path) { + inputs.push(link_path); + } + } LinkPtr::Object(_) => {} LinkPtr::Interface(_) => {} } @@ -510,6 +550,200 @@ fn add_object_lib_target( // Omit phony rules for object libraries } +fn add_shared_lib_target( + lib: &Arc, + generator_opts: &GeneratorOpts, + rules: &mut NinjaRules, + build_lines: &mut Vec, + link_targets: &mut HashMap>, +) -> Result<(), String> { + let GeneratorOpts { + toolchain, + build_dir, + profile, + global_opts, + target_platform, + star_context, + .. + } = generator_opts; + + log::debug!(" shared lib target: {}", lib.name); + let mut inputs = Vec::::new(); + + let generator_vars = if let Some(gen_func) = &lib.generator_vars { + eval_vars(gen_func, star_context.clone(), "generator_vars")? + } else { + StarGeneratorVars::default() + }; + let mut includes = lib.internal_includes(); + includes.extend( + generator_vars + .include_dirs + .iter() + .map(|x| join_parent(&lib.project().info.path, x).full), + ); + let sources = lib + .sources + .extended_with(Sources::from_slice(&generator_vars.sources, &lib.project().info.path)?); + let mut defines = lib.internal_defines(); + defines.extend_from_slice(&generator_vars.defines); + + let source_data = SourceData { includes, defines }; + + if !sources.c.is_empty() { + let c_compiler = get_c_compiler(toolchain, lib.name())?; + let rule_compile_c = if let Some(rule) = &rules.compile_c_object { + rule + } else { + rules.compile_c_object = Some(compile_c_object(c_compiler)); + rules.compile_c_object.as_ref().unwrap() + }; + let mut c_compile_opts = profile.c_compile_flags.clone(); + if let Some(c_std) = &global_opts.c_standard { + c_compile_opts.push(c_compiler.c_std_flag(c_std)?); + } + if let Some(true) = global_opts.position_independent_code { + if let Some(fpic_flag) = c_compiler.position_independent_executable_flag() { + c_compile_opts.push(fpic_flag); + } + } + for src in &sources.c { + build_lines.push(add_obj_source( + input_path(&src.full, &lib.project().info.path), + &source_data, + output_subfolder_path( + build_dir, + &lib.project().info.name, + &lib.name, + &src.name, + &target_platform.obj_ext, + ), + rule_compile_c.name.clone(), + c_compile_opts.clone(), + &mut inputs, + )); + } + } + if !sources.cpp.is_empty() { + let cpp_compiler = get_cpp_compiler(toolchain, lib.name())?; + let rule_compile_cpp = if let Some(rule) = &rules.compile_cpp_object { + rule + } else { + rules.compile_cpp_object = Some(compile_cpp_object(cpp_compiler)); + rules.compile_cpp_object.as_ref().unwrap() + }; + let mut cpp_compile_opts = profile.cpp_compile_flags.clone(); + if let Some(cpp_std) = &global_opts.cpp_standard { + cpp_compile_opts.push(cpp_compiler.cpp_std_flag(cpp_std)?); + } + if let Some(true) = global_opts.position_independent_code { + if let Some(fpic_flag) = cpp_compiler.position_independent_executable_flag() { + cpp_compile_opts.push(fpic_flag); + } + } + for src in &sources.cpp { + build_lines.push(add_obj_source( + input_path(&src.full, &lib.project().info.path), + &source_data, + output_subfolder_path( + build_dir, + &lib.project().info.name, + &lib.name, + &src.name, + &target_platform.obj_ext, + ), + rule_compile_cpp.name.clone(), + cpp_compile_opts.clone(), + &mut inputs, + )); + } + } + if !sources.nasm.is_empty() { + let nasm_assembler = get_nasm_assembler(toolchain, lib.name())?; + let rule = if let Some(rule) = &rules.assemble_nasm_object { + rule + } else { + rules.assemble_nasm_object = Some(assemble_nasm_object(nasm_assembler)); + rules.assemble_nasm_object.as_ref().unwrap() + }; + let nasm_assemble_opts = &profile.nasm_assemble_flags; + for src in &sources.nasm { + build_lines.push(add_obj_source( + input_path(&src.full, &lib.project().info.path), + &source_data, + output_subfolder_path( + build_dir, + &lib.project().info.name, + &lib.name, + &src.name, + &target_platform.obj_ext, + ), + rule.name.clone(), + nasm_assemble_opts.clone(), + &mut inputs, + )); + } + } + for link in &lib.internal_links() { + let link_outputs = match link_targets.get(link) { + Some(x) => x, + None => return Err(format!("Output target not found: {} ({})", link.name(), lib.name())), + }; + inputs.extend_from_slice(link_outputs); + } + // Prevent the same lib from being added to the command more than once. + let inputs = deduplicate(inputs); + let rule_name = match &rules.link_shared { + Some(x) => x.name.clone(), + None => { + let linker = match &toolchain.shared_linker { + Some(x) => x, + None => { + return Err(format!( + "No shared linker specified in toolchain. A shared linker is required to build \"{}\".", + lib.name() + )); + } + }; + let link_rule = link_shared(linker.as_ref()); + let rule_name = link_rule.name.clone(); + rules.link_shared = Some(link_rule); + rule_name + } + }; + let mut link_flags = Vec::new(); + link_flags.extend(lib.internal_link_flags()); + if let Some(soname_flag) = target_platform.shared_runtime_identity_flags(lib.output_name()) { + link_flags.push(soname_flag); + } + let runtime_artifact_path = + output_path(build_dir, &lib.project().info.name, lib.name.as_ref(), &target_platform.shared_lib_ext); // foo.dll + let link_artifact_path = + output_path(build_dir, &lib.project().info.name, lib.name.as_ref(), &target_platform.shared_link_ext); // foo.lib + let output_targets = if link_artifact_path != runtime_artifact_path { + vec![runtime_artifact_path.clone(), link_artifact_path.clone()] + } else { + vec![runtime_artifact_path.clone()] + }; + link_targets.insert(LinkPtr::Shared(lib.clone()), vec![link_artifact_path.clone()]); + build_lines.push(NinjaBuild { + inputs, + output_targets, + rule_name, + keyval_set: HashMap::from([ + ("TARGET_FILE".to_string(), vec![runtime_artifact_path.clone()]), + ("LINK_FLAGS".to_string(), link_flags), + ]), + }); + build_lines.push(NinjaBuild { + inputs: vec![runtime_artifact_path], + output_targets: vec![lib.name.clone()], + rule_name: "phony".to_owned(), + keyval_set: HashMap::new(), + }); + Ok(()) +} + fn add_executable_target( exe: &Arc, generator_opts: &GeneratorOpts, @@ -644,17 +878,32 @@ fn add_executable_target( )); } } + + let mut rpaths = IndexSet::new(); for link in &exe.links { let link_outputs = match link_targets.get(link) { Some(x) => x, - None => return Err(format!("Output target not found: {}", link.name())), + None => return Err(format!("Output target not found1: {}", link.name())), }; inputs.extend_from_slice(link_outputs); + match link { + LinkPtr::Shared(_) => { + rpaths.extend(link_outputs.iter().map(|p| { + std::path::PathBuf::from(p) + .parent() + .unwrap() + .to_string_lossy() + .into_owned() + })); + } + _ => {} + }; + for translink in &link.public_links_recursive() { let link_outputs = match link_targets.get(translink) { Some(x) => x, - None => return Err(format!("Transitive output target not found: {}", translink.name())), + None => return Err(format!("Transitive output target not found: {} ({})", translink.name(), exe.name)), }; inputs.extend_from_slice(link_outputs); } @@ -680,6 +929,10 @@ fn add_executable_target( } }; let mut link_exe_flags = Vec::new(); + if !rpaths.is_empty() { + let rpaths = rpaths.into_iter().collect::>(); + link_exe_flags.extend(target_platform.runtime_search_path_flags(&rpaths)); + } if let Some(true) = global_opts.position_independent_code { if let Some(pie_flag) = toolchain .exe_linker @@ -1021,6 +1274,7 @@ fn test_position_independent_code() { static_libraries: vec![create_lib(weak_parent)], object_libraries: Vec::new(), interface_libraries: Vec::new(), + shared_libraries: Vec::new(), }); let toolchain = Toolchain { msvc_platforms: vec!["x64".to_owned(), "Win32".to_owned(), "ARM64".to_owned()], @@ -1037,11 +1291,7 @@ fn test_position_independent_code() { cpp_standard: Some("17".to_owned()), position_independent_code: Some(true), }; - let target_platform = TargetPlatform { - obj_ext: ".o".to_owned(), - static_lib_ext: ".a".to_owned(), - exe_ext: String::new(), - }; + let target_platform = TargetPlatform::from_target_triple("x86_64-unknown-linux-gnu"); let mut rules = NinjaRules::default(); let mut build_lines = Vec::new(); let generator_opts = GeneratorOpts { diff --git a/src/generator/xcode.rs b/src/generator/xcode.rs index ea7f79b..f06b361 100644 --- a/src/generator/xcode.rs +++ b/src/generator/xcode.rs @@ -24,6 +24,7 @@ use crate::{ index_set::IndexSet, }, project::{Project, ProjectInfo}, + shared_library::SharedLibrary, target::{LinkTarget, Target}, toolchain::{ PbxItem, @@ -789,6 +790,32 @@ struct GlobalTargetMeta { target_name: String, product_name: String, local_key: Key, + product_kind: XcodeProductKind, +} + +#[derive(Clone, Copy)] +enum XcodeProductKind { + StaticArchive, + DynamicLibrary, + Executable, +} + +impl XcodeProductKind { + fn built_products_filename(self, output_basename: &str) -> String { + match self { + Self::StaticArchive => format!("lib{}.a", output_basename), + Self::DynamicLibrary => format!("{}.dylib", output_basename), + Self::Executable => output_basename.to_owned(), + } + } + + fn reference_proxy_explicit_type(self) -> ExplicitFileType { + match self { + XcodeProductKind::StaticArchive => ExplicitFileType::Archive, + XcodeProductKind::DynamicLibrary => ExplicitFileType::Dylib, + XcodeProductKind::Executable => ExplicitFileType::Executable, + } + } } fn transform_build_graph_to_xcode_graphs( @@ -1072,6 +1099,7 @@ fn link_as_key(link: &LinkPtr) -> *const dyn Target { LinkPtr::Static(x) => as_key(x), LinkPtr::Object(x) => as_key(x), LinkPtr::Interface(x) => as_key(x), + LinkPtr::Shared(x) => as_key(x), } } @@ -1111,6 +1139,7 @@ fn project_targets( target_name: nt.name.clone(), product_name: nt.product_name.clone(), local_key: key, + product_kind: XcodeProductKind::StaticArchive, }, ); native_target_keys.push(key); @@ -1140,6 +1169,35 @@ fn project_targets( target_name: nt.name.clone(), product_name: nt.product_name.clone(), local_key: key, + product_kind: XcodeProductKind::StaticArchive, + }, + ); + native_target_keys.push(key); + } + + for lib in &project.shared_libraries { + let key = new_native_target_shared( + lib, + native_target_build_configs, + toolchain, + graph, + id_gen, + global_targets, + &mut external_projects, + build_dir, + )?; + let nt = graph.native_targets.get(&key).unwrap(); + let prod_ref = graph.file_references.get(&nt.product_reference).unwrap(); + global_targets.insert( + as_key(lib), + GlobalTargetMeta { + project_info: project.info.clone(), + native_target_id: nt.id.clone(), + product_reference_id: prod_ref.id.clone(), + target_name: nt.name.clone(), + product_name: nt.product_name.clone(), + local_key: key, + product_kind: XcodeProductKind::DynamicLibrary, }, ); native_target_keys.push(key); @@ -1167,6 +1225,7 @@ fn project_targets( target_name: nt.name.clone(), product_name: nt.product_name.clone(), local_key: key, + product_kind: XcodeProductKind::Executable, }, ); native_target_keys.push(key); @@ -1194,12 +1253,12 @@ fn new_native_target_archive( file_type: FileRefType::Explicit(ExplicitFileType::Archive), include_in_index: Some(false), name: None, - path: format!("lib{}.a", target.output_name()), + path: XcodeProductKind::StaticArchive.built_products_filename(target.output_name()), source_tree: "BUILT_PRODUCTS_DIR".to_owned(), }, ); - let (dependencies, build_phases, build_rules) = new_native_target_common( + let (dependencies, build_phases, build_rules, _has_shared_runtime_deps) = new_native_target_common( target, sources, generator_vars, @@ -1227,6 +1286,9 @@ fn new_native_target_archive( include_dirs, defines, target.name().to_owned(), + XcodeProductKind::StaticArchive, + target.output_name().to_owned(), + false, id_gen, ), build_phases, @@ -1241,6 +1303,75 @@ fn new_native_target_archive( Ok(native_target_key) } +fn new_native_target_shared( + lib: &Arc, + native_target_build_configs: &[XCBuildConfiguration], + toolchain: &Toolchain, + graph: &mut SubGraph, + id_gen: &mut IdGenerator, + global_targets: &HashMap<*const dyn Target, GlobalTargetMeta>, + external_projects: &mut HashMap, + build_dir: &Path, +) -> Result { + let product_reference = id_gen.next(); + let out_name = lib.output_name(); + graph.file_references.insert( + product_reference, + PBXFileReference { + id: id_gen.new_id(), + file_type: FileRefType::Explicit(ExplicitFileType::Dylib), + include_in_index: Some(false), + name: None, + path: XcodeProductKind::DynamicLibrary.built_products_filename(out_name), + source_tree: "BUILT_PRODUCTS_DIR".to_owned(), + }, + ); + + let (dependencies, build_phases, build_rules, has_shared_runtime_deps) = new_native_target_common( + lib.as_ref(), + &lib.sources, + &lib.generator_vars, + toolchain, + graph, + id_gen, + global_targets, + external_projects, + build_dir, + )?; + + let include_dirs = lib + .internal_includes() + .into_iter() + .map(|src| src.to_string_lossy().to_string()) + .collect::>(); + let defines = lib.internal_defines(); + + let native_target_key = id_gen.next(); + let native_target = PBXNativeTarget { + id: id_gen.new_id(), + build_configuration_list: clone_xc_with( + native_target_build_configs, + graph, + include_dirs, + defines, + lib.name.clone(), + XcodeProductKind::DynamicLibrary, + lib.output_name().to_owned(), + has_shared_runtime_deps, + id_gen, + ), + build_phases, + build_rules, + dependencies, + name: lib.name.clone(), + product_name: lib.output_name().to_owned(), + product_reference, + product_type: ProductType::LibraryDynamic, + }; + graph.native_targets.insert(native_target_key, native_target); + Ok(native_target_key) +} + fn new_native_target_executable( exe: &Executable, native_target_build_configs: &[XCBuildConfiguration], @@ -1259,12 +1390,12 @@ fn new_native_target_executable( file_type: FileRefType::Explicit(ExplicitFileType::Executable), include_in_index: Some(false), name: None, - path: exe.name.clone(), + path: XcodeProductKind::Executable.built_products_filename(exe.name()), source_tree: "BUILT_PRODUCTS_DIR".to_owned(), }, ); - let (dependencies, build_phases, build_rules) = new_native_target_common( + let (dependencies, build_phases, build_rules, needs_shared_runtime) = new_native_target_common( exe, &exe.sources, &exe.generator_vars, @@ -1292,6 +1423,9 @@ fn new_native_target_executable( include_dirs, defines, exe.name.clone(), + XcodeProductKind::Executable, + exe.output_name().to_owned(), + needs_shared_runtime, id_gen, ), build_phases, @@ -1316,7 +1450,7 @@ fn new_native_target_common( global_targets: &HashMap<*const dyn Target, GlobalTargetMeta>, external_projects: &mut HashMap, build_dir: &Path, -) -> Result<(Vec, Vec, Vec), String> { +) -> Result<(Vec, Vec, Vec, bool), String> { if generator_vars.is_some() { return Err("generator_vars are not supported with Xcode generator".to_owned()); } @@ -1331,6 +1465,7 @@ fn new_native_target_common( _ => physical_links.insert(link), } } + let has_shared_runtime_deps = physical_links.iter().any(|link| matches!(link, LinkPtr::Shared(_))); for link in physical_links { let dep_meta = match global_targets.get(&link_as_key(&link)) { Some(k) => k, @@ -1420,12 +1555,13 @@ fn new_native_target_common( ); let reference_proxy_key = id_gen.next(); - let ref_proxy_path = format!("lib{}.a", dep_meta.product_name); + let ref_proxy_path = dep_meta.product_kind.built_products_filename(&dep_meta.product_name); + let ref_proxy_file_type = dep_meta.product_kind.reference_proxy_explicit_type(); graph.reference_proxies.insert( reference_proxy_key, PBXReferenceProxy { id: id_gen.new_id(), - file_type: ExplicitFileType::Archive, + file_type: ref_proxy_file_type, name: None, path: ref_proxy_path.clone(), remote_ref: file_proxy_key, @@ -1509,7 +1645,7 @@ fn new_native_target_common( build_rule_keys.push(build_rule_key); } - Ok((dependencies, build_phase_keys, build_rule_keys)) + Ok((dependencies, build_phase_keys, build_rule_keys, has_shared_runtime_deps)) } fn get_or_create_external_project( @@ -1674,6 +1810,9 @@ fn clone_xc_with( include_dirs: Vec, defines: Vec, target_name: String, + product_kind: XcodeProductKind, + product_output_name: String, + add_runtime_runpaths: bool, id_gen: &mut IdGenerator, ) -> Key { let mut build_configuration_keys = Vec::new(); @@ -1704,6 +1843,19 @@ fn clone_xc_with( } else if !defines.is_empty() { build_settings.insert("GCC_PREPROCESSOR_DEFINITIONS".to_owned(), BuildSetting::Array(defines.clone())); } + match product_kind { + XcodeProductKind::DynamicLibrary => { + ensure_dylib_install_name(&mut build_settings, product_kind, &product_output_name); + if add_runtime_runpaths { + append_deduplicated_runpaths(&mut build_settings, &["@loader_path"]); + } + } + XcodeProductKind::Executable if add_runtime_runpaths => { + append_deduplicated_runpaths(&mut build_settings, &["@executable_path"]); + } + XcodeProductKind::Executable => {} + XcodeProductKind::StaticArchive => {} + } let build_cfg_key = id_gen.next(); graph.build_configurations.insert( @@ -1738,6 +1890,41 @@ fn clone_xc_with( cfg_list_key } +fn ensure_dylib_install_name( + build_settings: &mut BTreeMap, + product_kind: XcodeProductKind, + product_output_name: &str, +) { + let dylib_name = product_kind.built_products_filename(product_output_name); + build_settings + .entry("LD_DYLIB_INSTALL_NAME".to_owned()) + .or_insert(BuildSetting::Single(format!("@rpath/{dylib_name}"))); +} + +fn append_deduplicated_runpaths(build_settings: &mut BTreeMap, additional_runpaths: &[&str]) { + let mut runpaths = existing_runpaths(build_settings); + for runpath in additional_runpaths { + runpaths.insert((*runpath).to_owned()); + } + build_settings.insert("LD_RUNPATH_SEARCH_PATHS".to_owned(), BuildSetting::Array(runpaths.into_iter().collect())); +} + +fn existing_runpaths(build_settings: &BTreeMap) -> IndexSet { + let mut runpaths = IndexSet::new(); + match build_settings.get("LD_RUNPATH_SEARCH_PATHS") { + Some(BuildSetting::Single(existing)) => { + runpaths.insert(existing.clone()); + } + Some(BuildSetting::Array(existing)) => { + for p in existing { + runpaths.insert(p.clone()); + } + } + None => {} + } + runpaths +} + struct PBXProject { pub id: String, // isa = PBXProject; @@ -1841,6 +2028,7 @@ impl core::fmt::Display for CompilerSpec { enum ProductType { LibraryStatic, + LibraryDynamic, Tool, } @@ -1848,6 +2036,7 @@ impl core::fmt::Display for ProductType { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { ProductType::LibraryStatic => f.write_str("com.apple.product-type.library.static"), + ProductType::LibraryDynamic => f.write_str("com.apple.product-type.library.dynamic"), ProductType::Tool => f.write_str("com.apple.product-type.tool"), } } @@ -2004,6 +2193,7 @@ enum FileRefType { enum ExplicitFileType { Archive, // archive.ar + Dylib, // compiled.mach-o.dylib Executable, // compiled.mach-o.executable } @@ -2012,6 +2202,7 @@ impl core::fmt::Display for ExplicitFileType { match self { // Why is only compiled.mach-o.executable surrounded in quotes? ExplicitFileType::Executable => f.write_str("\"compiled.mach-o.executable\""), + ExplicitFileType::Dylib => f.write_str("\"compiled.mach-o.dylib\""), ExplicitFileType::Archive => f.write_str("archive.ar"), } } @@ -2255,6 +2446,7 @@ fn test_pbxproj_generation() { static_libraries: vec![adder], object_libraries: vec![subtracter], interface_libraries: vec![arithmetic], + shared_libraries: Vec::new(), } }); @@ -2265,6 +2457,7 @@ fn test_pbxproj_generation() { cpp_compiler: Some(Box::new(TestCompiler {})), nasm_assembler: None, static_linker: Some(vec!["llvm-ar".to_owned()]), + shared_linker: None, exe_linker: Some(Box::new(TestCompiler {})), profile: BTreeMap::::from([( "Debug".to_owned(), @@ -2362,6 +2555,7 @@ fn test_xcode_transform() { static_libraries: vec![adder], object_libraries: Vec::new(), interface_libraries: vec![iface], + shared_libraries: Vec::new(), } }); diff --git a/src/lib.rs b/src/lib.rs index 89a1a9b..c90f298 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ mod link_type; mod misc; mod object_library; pub mod project; +mod shared_library; mod starlark_api; mod starlark_context; mod starlark_executable; @@ -15,6 +16,7 @@ mod starlark_interface_library; mod starlark_link_target; mod starlark_object_library; mod starlark_project; +mod starlark_shared_library; mod starlark_static_library; mod static_library; pub mod target; diff --git a/src/link_type.rs b/src/link_type.rs index 1cd4989..bef05c7 100644 --- a/src/link_type.rs +++ b/src/link_type.rs @@ -8,6 +8,7 @@ use crate::{ interface_library::InterfaceLibrary, object_library::ObjectLibrary, project::Project, + shared_library::SharedLibrary, static_library::StaticLibrary, target::{LinkTarget, Target}, }; @@ -17,6 +18,7 @@ pub enum LinkPtr { Static(Arc), Object(Arc), Interface(Arc), + Shared(Arc), } impl cmp::PartialEq for LinkPtr { @@ -31,6 +33,9 @@ impl cmp::PartialEq for LinkPtr { (Self::Interface(a), Self::Interface(b)) => { core::ptr::eq(Arc::as_ptr(a) as *const (), Arc::as_ptr(b) as *const ()) } + (Self::Shared(a), Self::Shared(b)) => { + core::ptr::eq(Arc::as_ptr(a) as *const (), Arc::as_ptr(b) as *const ()) + } _ => false, } } @@ -45,6 +50,7 @@ impl hash::Hash for LinkPtr { Self::Static(x) => (Arc::as_ptr(x) as *const ()).hash(hasher), Self::Object(x) => (Arc::as_ptr(x) as *const ()).hash(hasher), Self::Interface(x) => (Arc::as_ptr(x) as *const ()).hash(hasher), + Self::Shared(x) => (Arc::as_ptr(x) as *const ()).hash(hasher), } } } @@ -55,6 +61,7 @@ impl Target for LinkPtr { Self::Static(x) => x.name(), Self::Object(x) => x.name(), Self::Interface(x) => x.name(), + Self::Shared(x) => x.name(), } } fn output_name(&self) -> &str { @@ -62,6 +69,7 @@ impl Target for LinkPtr { Self::Static(x) => x.output_name(), Self::Object(x) => x.output_name(), Self::Interface(x) => x.output_name(), + Self::Shared(x) => x.output_name(), } } fn project(&self) -> Arc { @@ -69,6 +77,7 @@ impl Target for LinkPtr { Self::Static(x) => x.project(), Self::Object(x) => x.project(), Self::Interface(x) => x.project(), + Self::Shared(x) => x.project(), } } fn internal_includes(&self) -> Vec { @@ -76,6 +85,7 @@ impl Target for LinkPtr { Self::Static(x) => x.internal_includes(), Self::Object(x) => x.internal_includes(), Self::Interface(x) => x.internal_includes(), + Self::Shared(x) => x.internal_includes(), } } fn internal_defines(&self) -> Vec { @@ -83,6 +93,7 @@ impl Target for LinkPtr { Self::Static(x) => x.internal_defines(), Self::Object(x) => x.internal_defines(), Self::Interface(x) => x.internal_defines(), + Self::Shared(x) => x.internal_defines(), } } fn internal_link_flags(&self) -> Vec { @@ -90,6 +101,7 @@ impl Target for LinkPtr { Self::Static(x) => x.internal_link_flags(), Self::Object(x) => x.internal_link_flags(), Self::Interface(x) => x.internal_link_flags(), + Self::Shared(x) => x.internal_link_flags(), } } fn internal_links(&self) -> Vec { @@ -97,6 +109,7 @@ impl Target for LinkPtr { Self::Static(x) => x.internal_links(), Self::Object(x) => x.internal_links(), Self::Interface(x) => x.internal_links(), + Self::Shared(x) => x.internal_links(), } } } @@ -107,6 +120,7 @@ impl LinkTarget for LinkPtr { Self::Static(x) => x.public_includes_recursive(), Self::Object(x) => x.public_includes_recursive(), Self::Interface(x) => x.public_includes_recursive(), + Self::Shared(x) => x.public_includes_recursive(), } } @@ -115,6 +129,7 @@ impl LinkTarget for LinkPtr { Self::Static(x) => x.public_defines_recursive(), Self::Object(x) => x.public_defines_recursive(), Self::Interface(x) => x.public_defines_recursive(), + Self::Shared(x) => x.public_defines_recursive(), } } @@ -123,6 +138,7 @@ impl LinkTarget for LinkPtr { Self::Static(x) => x.public_link_flags_recursive(), Self::Object(x) => x.public_link_flags_recursive(), Self::Interface(x) => x.public_link_flags_recursive(), + Self::Shared(x) => x.public_link_flags_recursive(), } } @@ -131,6 +147,7 @@ impl LinkTarget for LinkPtr { Self::Static(x) => x.public_links(), Self::Object(x) => x.public_links(), Self::Interface(x) => x.public_links(), + Self::Shared(x) => x.public_links(), } } @@ -139,6 +156,7 @@ impl LinkTarget for LinkPtr { Self::Static(x) => x.public_links_recursive(), Self::Object(x) => x.public_links_recursive(), Self::Interface(x) => x.public_links_recursive(), + Self::Shared(x) => x.public_links_recursive(), } } } diff --git a/src/misc/index_set.rs b/src/misc/index_set.rs index 84cedcf..6ea4dc6 100644 --- a/src/misc/index_set.rs +++ b/src/misc/index_set.rs @@ -44,6 +44,19 @@ where } } + pub fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + for item in iter { + self.insert(item); + } + } + + pub fn is_empty(&self) -> bool { + self.vec.is_empty() + } + pub fn iter<'a>(&'a self) -> core::slice::Iter<'a, T> { self.vec.iter() } diff --git a/src/object_library.rs b/src/object_library.rs index fd3493b..6f6f1f6 100644 --- a/src/object_library.rs +++ b/src/object_library.rs @@ -244,6 +244,7 @@ mod tests { ], object_libraries: vec![main_lib], interface_libraries: Vec::new(), + shared_libraries: Vec::new(), } }); diff --git a/src/project.rs b/src/project.rs index e796dff..a45049f 100644 --- a/src/project.rs +++ b/src/project.rs @@ -7,6 +7,7 @@ use crate::{ executable::Executable, // interface_library::InterfaceLibrary, object_library::ObjectLibrary, + shared_library::SharedLibrary, static_library::StaticLibrary, }; @@ -24,4 +25,5 @@ pub struct Project { pub static_libraries: Vec>, pub object_libraries: Vec>, pub interface_libraries: Vec>, + pub shared_libraries: Vec>, } diff --git a/src/shared_library.rs b/src/shared_library.rs new file mode 100644 index 0000000..171ff9f --- /dev/null +++ b/src/shared_library.rs @@ -0,0 +1,149 @@ +use std::{ + path::PathBuf, // + sync::{Arc, Weak}, +}; + +use starlark::values::OwnedFrozenValue; + +use crate::{ + link_type::LinkPtr, + misc::{SourcePath, Sources}, + project::Project, // + target::{LinkTarget, Target}, +}; + +#[derive(Debug)] +pub struct SharedLibrary { + pub parent_project: Weak, + pub name: String, + pub sources: Sources, + pub link_private: Vec, + pub link_public: Vec, + pub include_dirs_public: Vec, + pub include_dirs_private: Vec, + pub defines_private: Vec, + pub defines_public: Vec, + pub link_flags_public: Vec, + + pub generator_vars: Option, + + pub output_name: Option, +} + +impl Target for SharedLibrary { + fn name(&self) -> &str { + &self.name + } + fn output_name(&self) -> &str { + match &self.output_name { + Some(output_name) => output_name, + None => &self.name, + } + } + fn project(&self) -> Arc { + self.parent_project.upgrade().unwrap() + } + fn internal_includes(&self) -> Vec { + let mut includes = crate::misc::index_set::IndexSet::new(); + for include in self.public_includes_recursive() { + includes.insert(include); + } + for include in self.include_dirs_private.iter().map(|x| x.full.clone()) { + includes.insert(include); + } + for link in &self.link_private { + for include in link.public_includes_recursive() { + includes.insert(include); + } + } + includes.into_iter().collect() + } + fn internal_defines(&self) -> Vec { + let mut defines = crate::misc::index_set::IndexSet::new(); + for def in self.public_defines_recursive() { + defines.insert(def); + } + for def in &self.defines_private { + defines.insert(def.clone()); + } + for link in &self.link_private { + for def in link.public_defines_recursive() { + defines.insert(def); + } + } + defines.into_iter().collect() + } + fn internal_link_flags(&self) -> Vec { + self.public_link_flags_recursive() + } + fn internal_links(&self) -> Vec { + self.public_links_recursive() + } +} + +impl LinkTarget for SharedLibrary { + fn public_includes_recursive(&self) -> Vec { + let mut includes = crate::misc::index_set::IndexSet::new(); + for link in &self.link_public { + for include in link.public_includes_recursive() { + includes.insert(include); + } + } + for include in &self.include_dirs_public { + includes.insert(include.full.clone()); + } + includes.into_iter().collect() + } + fn public_defines_recursive(&self) -> Vec { + let mut defines = crate::misc::index_set::IndexSet::new(); + for link in &self.link_public { + for def in link.public_defines_recursive() { + defines.insert(def); + } + } + for def in &self.defines_public { + defines.insert(def.clone()); + } + defines.into_iter().collect() + } + fn public_link_flags_recursive(&self) -> Vec { + let mut flags = crate::misc::index_set::IndexSet::new(); + for link in &self.link_public { + for flag in link.public_link_flags_recursive() { + flags.insert(flag); + } + } + for flag in &self.link_flags_public { + flags.insert(flag.clone()); + } + flags.into_iter().collect() + } + fn public_links(&self) -> Vec { + self.link_public.clone() + } + fn public_links_recursive(&self) -> Vec { + let mut links = Vec::new(); + // Static libraries have to be linked, even if they're private. + // The include dirs of the private links won't propagate though. + // Breadth-first addition + for link in &self.link_private { + links.push(link.clone()); + } + for link in &self.link_public { + links.push(link.clone()); + } + for link in &self.link_private { + links.extend(link.public_links_recursive()); + } + for link in &self.link_public { + links.extend(link.public_links_recursive()); + } + links + } +} + +impl SharedLibrary { + pub(crate) fn set_parent(&mut self, parent: Weak) { + self.parent_project = parent; + } +} diff --git a/src/starlark_api.rs b/src/starlark_api.rs index ede3990..22964e5 100644 --- a/src/starlark_api.rs +++ b/src/starlark_api.rs @@ -22,6 +22,7 @@ use crate::{ starlark_link_target::StarLinkTarget, starlark_object_library::{StarGeneratorVars, StarObjLibWrapper, StarObjectLibrary}, starlark_project::StarProject, + starlark_shared_library::{StarSharedLibWrapper, StarSharedLibrary}, starlark_static_library::{StarStaticLibWrapper, StarStaticLibrary}, }; @@ -81,6 +82,10 @@ fn get_link_targets(links: Vec) -> Result>, a Some(x) => link_targets.push(x.0.clone()), None => return err_msg(format!("Could not unpack \"link\" {}", link.get_type())), }, + "SharedLibrary" => match StarSharedLibWrapper::from_value(link) { + Some(x) => link_targets.push(x.0.clone()), + None => return err_msg(format!("Could not unpack \"link\" {}", link.get_type())), + }, "ObjectLibrary" => match StarObjLibWrapper::from_value(link) { Some(x) => link_targets.push(x.0.clone()), None => return err_msg(format!("Could not unpack \"link\" {}", link.get_type())), @@ -166,6 +171,7 @@ pub(crate) fn build_api(builder: &mut GlobalsBuilder) { project.object_libraries.push(lib.clone()); Ok(StarObjLibWrapper(lib)) } + fn add_interface_library<'v>( name: String, #[starlark(default = Default::default())] link: UnpackList>, @@ -193,6 +199,44 @@ pub(crate) fn build_api(builder: &mut GlobalsBuilder) { project.interface_libraries.push(lib.clone()); Ok(StarIfaceLibWrapper(lib)) } + + fn add_shared_library<'v>( + name: String, + sources: UnpackList, + #[starlark(default = Default::default())] link_private: UnpackList>, + #[starlark(default = Default::default())] link_public: UnpackList>, + #[starlark(default = Default::default())] include_dirs_private: UnpackList, + #[starlark(default = Default::default())] include_dirs_public: UnpackList, + #[starlark(default = Default::default())] defines_private: UnpackList, + #[starlark(default = Default::default())] defines_public: UnpackList, + #[starlark(default = Default::default())] link_flags_public: UnpackList, + generator_vars: Option>, + eval: &mut Evaluator<'v, '_, '_>, + ) -> anyhow::Result { + let state = eval + .extra + .unwrap() + .downcast_ref::() + .ok_or(anyhow::anyhow!("No state"))?; + let lib = Arc::new(StarSharedLibrary { + parent_project: Arc::downgrade(&state.project), + name, + sources: sources.items, + link_private: get_link_targets(link_private.items)?, + link_public: get_link_targets(link_public.items)?, + include_dirs_private: include_dirs_private.items, + include_dirs_public: include_dirs_public.items, + defines_private: defines_private.items, + defines_public: defines_public.items, + link_flags_public: link_flags_public.items, + generator_vars: generator_func(generator_vars, eval), + output_name: None, // TODO(Travers) + }); + let mut project = state.project.lock().map_err(|e| anyhow::anyhow!(e.to_string()))?; + project.shared_libraries.push(lib.clone()); + Ok(StarSharedLibWrapper(lib)) + } + fn add_executable<'v>( name: String, sources: UnpackList, diff --git a/src/starlark_project.rs b/src/starlark_project.rs index 8776e21..afb5296 100644 --- a/src/starlark_project.rs +++ b/src/starlark_project.rs @@ -30,10 +30,12 @@ use crate::{ link_type::LinkPtr, object_library::ObjectLibrary, project::{Project, ProjectInfo}, + shared_library::SharedLibrary, starlark_executable::StarExecutable, // starlark_interface_library::{StarIfaceLibWrapper, StarIfaceLibrary}, starlark_link_target::PtrLinkTarget, starlark_object_library::{StarObjLibWrapper, StarObjectLibrary}, + starlark_shared_library::{StarSharedLibWrapper, StarSharedLibrary}, starlark_static_library::{StarStaticLibWrapper, StarStaticLibrary}, static_library::StaticLibrary, }; @@ -47,6 +49,7 @@ pub(super) struct StarProject { pub static_libraries: Vec>, pub object_libraries: Vec>, pub interface_libraries: Vec>, + pub shared_libraries: Vec>, pub generator_names: HashMap, } @@ -86,6 +89,11 @@ impl<'v> StarlarkValue<'v> for StarProject { return Some(heap.alloc(StarIfaceLibWrapper(lib.clone()))); } } + for lib in &self.shared_libraries { + if lib.name == attribute { + return Some(heap.alloc(StarSharedLibWrapper(lib.clone()))); + } + } None } fn has_attr(&self, attribute: &str, _: &'v Heap) -> bool { @@ -129,6 +137,7 @@ pub(super) struct StarLinkTargetCache { static_libs: HashMap>, object_libs: HashMap>, interface_libs: HashMap>, + shared_libs: HashMap>, } impl StarLinkTargetCache { @@ -138,6 +147,7 @@ impl StarLinkTargetCache { static_libs: HashMap::new(), object_libs: HashMap::new(), interface_libs: HashMap::new(), + shared_libs: HashMap::new(), } } pub fn get_static(&self, key: &PtrLinkTarget) -> Option<&Arc> { @@ -161,6 +171,13 @@ impl StarLinkTargetCache { None } } + pub fn get_shared(&self, key: &PtrLinkTarget) -> Option<&Arc> { + if self.all_targets.contains(key) { + self.shared_libs.get(key) + } else { + None + } + } pub fn get(&self, key: &PtrLinkTarget) -> Option { if let Some(x) = self.get_static(key) { return Some(LinkPtr::Static(x.clone())); @@ -171,6 +188,9 @@ impl StarLinkTargetCache { if let Some(x) = self.get_interface(key) { return Some(LinkPtr::Interface(x.clone())); } + if let Some(x) = self.get_shared(key) { + return Some(LinkPtr::Shared(x.clone())); + } None } pub fn insert_static(&mut self, key: PtrLinkTarget, value: Arc) { @@ -185,6 +205,10 @@ impl StarLinkTargetCache { self.interface_libs.insert(key.clone(), value); self.all_targets.insert(key); } + pub fn insert_shared(&mut self, key: PtrLinkTarget, value: Arc) { + self.shared_libs.insert(key.clone(), value); + self.all_targets.insert(key); + } } impl StarProject { @@ -197,6 +221,7 @@ impl StarProject { static_libraries: Vec::new(), object_libraries: Vec::new(), interface_libraries: Vec::new(), + shared_libraries: Vec::new(), generator_names: HashMap::new(), } @@ -268,6 +293,21 @@ impl StarProject { } }) .collect::>()?, + shared_libraries: self + .shared_libraries + .iter() + .map(|x| -> Result,String>{ + let ptr = PtrLinkTarget(x.clone()); + if let Some(lib) = link_map.get_shared(&ptr) { + Ok(lib.clone()) + } else { + let data = x.as_library(Weak::new(), &self.path, link_map, &self.generator_names)?; + let arc = Arc::new(data); + link_map.insert_shared(ptr, arc.clone()); + Ok(arc) + } + }) + .collect::>()?, }; //); let ret = Arc::::new_cyclic(move |weak_parent: &Weak| -> Project { @@ -289,6 +329,10 @@ impl StarProject { let lib_mut = unsafe { &mut (*Arc::as_ptr(lib).cast_mut()) }; lib_mut.set_parent(weak_parent.clone()); } + for lib in &mut project.shared_libraries { + let lib_mut = unsafe { &mut (*Arc::as_ptr(lib).cast_mut()) }; + lib_mut.set_parent(weak_parent.clone()); + } project }); diff --git a/src/starlark_shared_library.rs b/src/starlark_shared_library.rs new file mode 100644 index 0000000..6aff519 --- /dev/null +++ b/src/starlark_shared_library.rs @@ -0,0 +1,221 @@ +use core::fmt; +use std::{ + collections::HashMap, + path::Path, + sync::{Arc, Mutex, Weak}, +}; + +use allocative::Allocative; +use starlark::{ + environment::{ + Methods, // + MethodsBuilder, + MethodsStatic, + }, + starlark_module, // + starlark_simple_value, + values::{ + Heap, // + NoSerialize, + OwnedFrozenValue, + ProvidesStaticType, + StarlarkValue, + StringValue, + Value, + }, +}; + +use super::{ + link_type::LinkPtr, + misc::{Sources, join_parent}, + project::Project, + shared_library::SharedLibrary, + starlark_fmt::{format_link_targets, format_strings}, + starlark_link_target::{PtrLinkTarget, StarLinkTarget}, + starlark_project::{StarLinkTargetCache, StarProject}, +}; + +#[derive(Clone, Debug, ProvidesStaticType, Allocative)] +pub(super) struct StarSharedLibrary { + pub parent_project: Weak>, + pub name: String, + pub sources: Vec, + pub link_private: Vec>, + pub link_public: Vec>, + pub include_dirs_public: Vec, + pub include_dirs_private: Vec, + pub defines_private: Vec, + pub defines_public: Vec, + pub link_flags_public: Vec, + + pub generator_vars: Option, + + pub output_name: Option, +} + +impl fmt::Display for StarSharedLibrary { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + r#"SharedLibrary {{ + name: "{}", + sources: [{}], + link_private: [{}], + link_public: [{}], + include_dirs_public: [{}], + include_dirs_private: [{}], + defines_private: [{}], + defines_public: [{}], + link_flags_public: [{}], + generator_vars: {}, +}}"#, + self.name, + format_strings(&self.sources), + format_link_targets(&self.link_private), + format_link_targets(&self.link_public), + format_strings(&self.include_dirs_public), + format_strings(&self.include_dirs_private), + format_strings(&self.defines_private), + format_strings(&self.defines_public), + format_strings(&self.link_flags_public), + if self.generator_vars.is_some() { + "(generated)" + } else { + "None" + }, + ) + } +} + +impl StarLinkTarget for StarSharedLibrary { + fn as_link_target( + &self, + parent: Weak, + parent_path: &Path, + ptr: PtrLinkTarget, + link_map: &mut StarLinkTargetCache, + gen_name_map: &HashMap, + ) -> Result { + let arc = Arc::new(self.as_library(parent, parent_path, link_map, gen_name_map)?); + // let ptr = PtrLinkTarget(arc.clone()); + link_map.insert_shared(ptr, arc.clone()); + Ok(LinkPtr::Shared(arc)) + } + + fn name(&self) -> String { + self.name.clone() + } + + fn public_includes_recursive(&self) -> Vec { + self.include_dirs_private.clone() + // for link in &self.link_public { + // public_includes.extend(link.public_includes_recursive()); + // } + // public_includes + } +} + +impl StarSharedLibrary { + pub fn as_library( + &self, + parent_project: Weak, + parent_path: &Path, + link_map: &mut StarLinkTargetCache, + gen_name_map: &HashMap, + ) -> Result { + Ok(SharedLibrary { + parent_project: parent_project.clone(), + name: self.name.clone(), + sources: Sources::from_slice(&self.sources, parent_path)?, + include_dirs_private: self + .include_dirs_private + .iter() + .map(|x| join_parent(parent_path, x)) + .collect(), + include_dirs_public: self + .include_dirs_public + .iter() + .map(|x| join_parent(parent_path, x)) + .collect(), + link_private: self + .link_private + .iter() + .map(|x| { + let ptr = PtrLinkTarget(x.clone()); + if let Some(lt) = link_map.get(&ptr) { + Ok(lt) + } else { + x.as_link_target(parent_project.clone(), parent_path, ptr, link_map, gen_name_map) + } + }) + .collect::>()?, + link_public: self + .link_public + .iter() + .map(|x| { + let ptr = PtrLinkTarget(x.clone()); + if let Some(lt) = link_map.get(&ptr) { + Ok(lt) + } else { + x.as_link_target(parent_project.clone(), parent_path, ptr, link_map, gen_name_map) + } + }) + .collect::>()?, + defines_private: self.defines_private.clone(), + defines_public: self.defines_public.clone(), + link_flags_public: self.link_flags_public.clone(), + generator_vars: match &self.generator_vars { + None => None, + Some(id) => match gen_name_map.get(id) { + Some(x) => Some(x.clone()), + None => return Err(format!("Could not find generator id in map: {}", id)), + }, + }, + output_name: self.output_name.clone(), + }) + } +} + +#[derive(Clone, Debug, ProvidesStaticType, NoSerialize, Allocative)] +pub(super) struct StarSharedLibWrapper(pub(super) Arc); + +impl fmt::Display for StarSharedLibWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +#[starlark::values::starlark_value(type = "SharedLibrary")] +impl<'v> StarlarkValue<'v> for StarSharedLibWrapper { + fn get_methods() -> Option<&'static Methods> { + library_methods() + } + fn get_attr(&self, attribute: &str, heap: &'v Heap) -> Option> { + match attribute { + "include_dirs" => Some(heap.alloc(self.0.public_includes_recursive())), + _ => None, + } + } + fn has_attr(&self, attribute: &str, _: &'v Heap) -> bool { + attribute == "include_dirs" + } + + fn dir_attr(&self) -> Vec { + let attrs = vec!["include_dirs".to_owned()]; + attrs + } +} + +starlark_simple_value!(StarSharedLibWrapper); + +#[starlark_module] +fn library_methods_impl(builder: &mut MethodsBuilder) { + fn name<'v>(this: &'v StarSharedLibWrapper, heap: &'v Heap) -> anyhow::Result> { + Ok(heap.alloc_str(&format!(":{}", this.0.name))) + } +} + +fn library_methods() -> Option<&'static Methods> { + static RES: MethodsStatic = MethodsStatic::new(); + RES.methods(library_methods_impl) +} diff --git a/src/static_library.rs b/src/static_library.rs index f582c30..8dbf356 100644 --- a/src/static_library.rs +++ b/src/static_library.rs @@ -245,6 +245,7 @@ mod tests { ], object_libraries: Vec::new(), interface_libraries: Vec::new(), + shared_libraries: Vec::new(), } }); diff --git a/src/toolchain.rs b/src/toolchain.rs index 9e3b6ad..84a053e 100644 --- a/src/toolchain.rs +++ b/src/toolchain.rs @@ -21,6 +21,7 @@ pub struct ToolchainFile { cpp_compiler: Option>, nasm_assembler: Option>, static_linker: Option>, + shared_linker: Option>, exe_linker: Option>, profile: Option>, // env: Option> @@ -34,6 +35,7 @@ pub struct Toolchain { pub cpp_compiler: Option>, pub nasm_assembler: Option>, pub static_linker: Option>, + pub shared_linker: Option>, pub exe_linker: Option>, pub profile: BTreeMap, } @@ -147,6 +149,8 @@ pub fn get_toolchain(toolchain_path: &Path, for_msvc: bool) -> Result match identify_linker(x) { Ok(linker) => Some(linker), @@ -176,6 +180,7 @@ pub fn get_toolchain(toolchain_path: &Path, for_msvc: bool) -> Result:Debug>") add_subdirectory(submodules/blobject) add_subdirectory(submodules/my_depend) +add_subdirectory(submodules/nasmproj) -add_library(mylib STATIC mylib.cpp) +add_library(mylib SHARED mylib.cpp) target_link_libraries(mylib PRIVATE my_depend_lib) add_executable(myexe main.cpp) target_compile_definitions(myexe PRIVATE MY_DEFINE="abc def") +target_link_libraries(myexe PRIVATE mylib my_depend_lib blobject nasmobjs zstd) diff --git a/test_data/test_01/build.catapult b/test_data/test_01/build.catapult index 594efc4..59ee5e0 100644 --- a/test_data/test_01/build.catapult +++ b/test_data/test_01/build.catapult @@ -1,7 +1,8 @@ -mylib = add_static_library( +mylib = add_shared_library( name = "mylib", sources = ["mylib.cpp"], + defines_private = ["MYLIB_EXPORT"], include_dirs_public = ["."], link_private = [my_depend.my_depend_lib], ) diff --git a/test_data/test_01/mylib.cpp b/test_data/test_01/mylib.cpp index 650875d..272caa3 100644 --- a/test_data/test_01/mylib.cpp +++ b/test_data/test_01/mylib.cpp @@ -1,3 +1,5 @@ +#include "mylib.hpp" + #include "my_depend.hpp" int add_two(int a) { diff --git a/test_data/test_01/mylib.hpp b/test_data/test_01/mylib.hpp index 36dd688..e585611 100644 --- a/test_data/test_01/mylib.hpp +++ b/test_data/test_01/mylib.hpp @@ -1,3 +1,13 @@ #pragma once -int add_two(int a); +#if defined _WIN32 || defined __CYGWIN__ + #ifdef MYLIB_EXPORT + #define EXPORT __declspec(dllexport) + #else + #define EXPORT __declspec(dllimport) + #endif +#else + #define EXPORT __attribute__ ((visibility ("default"))) +#endif + +EXPORT int add_two(int a); diff --git a/test_data/test_01/submodules/blobject/CMakeLists.txt b/test_data/test_01/submodules/blobject/CMakeLists.txt index 05b4ecb..64417b7 100644 --- a/test_data/test_01/submodules/blobject/CMakeLists.txt +++ b/test_data/test_01/submodules/blobject/CMakeLists.txt @@ -4,4 +4,7 @@ add_library(blobject OBJECT blobject2.c blobject.h ) -target_include_directories(blobject PUBLIC .) +target_include_directories(blobject + PRIVATE utils + PUBLIC . +) diff --git a/test_data/test_01/submodules/my_depend/CMakeLists.txt b/test_data/test_01/submodules/my_depend/CMakeLists.txt index bdecbdf..37867e2 100644 --- a/test_data/test_01/submodules/my_depend/CMakeLists.txt +++ b/test_data/test_01/submodules/my_depend/CMakeLists.txt @@ -3,4 +3,7 @@ add_library(my_depend_lib STATIC my_depend.cpp my_depend.hpp ) -target_include_directories(my_depend_lib PUBLIC .) +target_include_directories(my_depend_lib + PRIVATE utils + PUBLIC . +) diff --git a/test_data/test_01/submodules/nasmproj/CMakeLists.txt b/test_data/test_01/submodules/nasmproj/CMakeLists.txt new file mode 100644 index 0000000..2c51df2 --- /dev/null +++ b/test_data/test_01/submodules/nasmproj/CMakeLists.txt @@ -0,0 +1,9 @@ + +add_library(nasmobjs OBJECT + nasmproj.h + nasmsrc.asm +) + +target_include_directories(my_depend_lib + PUBLIC . +) diff --git a/test_data/test_xcode_basic/submodule/build.catapult b/test_data/test_xcode_basic/submodule/build.catapult index 97e2a14..c045091 100644 --- a/test_data/test_xcode_basic/submodule/build.catapult +++ b/test_data/test_xcode_basic/submodule/build.catapult @@ -1,5 +1,5 @@ -adder = add_static_library( +adder = add_shared_library( name = "adder", sources = ["adder/add.cpp"], include_dirs_private = ["utils"], diff --git a/test_data/toolchain_clang.toml b/test_data/toolchain_clang.toml index c5a741d..5a885be 100644 --- a/test_data/toolchain_clang.toml +++ b/test_data/toolchain_clang.toml @@ -2,6 +2,7 @@ c_compiler = ["clang"] cpp_compiler = ["clang++"] nasm_assembler = ["nasm", "-felf64"] static_linker = ["llvm-ar", "qc"] +shared_linker = ["clang++", "-shared"] exe_linker = ["clang++"] [profile.Debug] diff --git a/test_data/toolchain_clang_win.toml b/test_data/toolchain_clang_win.toml index ccaf4f3..e5b4f51 100644 --- a/test_data/toolchain_clang_win.toml +++ b/test_data/toolchain_clang_win.toml @@ -2,6 +2,7 @@ c_compiler = ["clang"] cpp_compiler = ["clang++"] nasm_assembler = ["C:/Program Files/NASM/nasm", "-fwin64"] static_linker = ["llvm-ar", "qc"] +shared_linker = ["clang++", "-shared"] exe_linker = ["clang++"] [profile.Debug] diff --git a/test_data/toolchain_emscripten.toml b/test_data/toolchain_emscripten.toml index 66c8b22..e2b3a7c 100644 --- a/test_data/toolchain_emscripten.toml +++ b/test_data/toolchain_emscripten.toml @@ -1,6 +1,7 @@ c_compiler = ["emcc"] cpp_compiler = ["em++"] static_linker = ["emar", "qc"] +shared_linker = ["em++", "-shared"] exe_linker = ["em++"] [profile.Debug] diff --git a/test_data/toolchain_gcc.toml b/test_data/toolchain_gcc.toml index 184d784..b592d0a 100644 --- a/test_data/toolchain_gcc.toml +++ b/test_data/toolchain_gcc.toml @@ -2,6 +2,7 @@ c_compiler = ["gcc"] cpp_compiler = ["g++"] nasm_assembler = ["nasm", "-felf64"] static_linker = ["ar", "qc"] +shared_linker = ["g++", "-shared"] exe_linker = ["g++"] [profile.Debug] diff --git a/test_data/toolchain_ninja_mac.toml b/test_data/toolchain_ninja_mac.toml index d629240..63f937f 100644 --- a/test_data/toolchain_ninja_mac.toml +++ b/test_data/toolchain_ninja_mac.toml @@ -2,6 +2,7 @@ c_compiler = ["/opt/homebrew/opt/llvm/bin/clang"] cpp_compiler = ["/opt/homebrew/opt/llvm/bin/clang++"] nasm_assembler = ["nasm", "-fmacho64"] static_linker = ["/opt/homebrew/opt/llvm/bin/llvm-ar", "qc"] +shared_linker = ["/opt/homebrew/opt/llvm/bin/clang++", "-shared"] exe_linker = ["/opt/homebrew/opt/llvm/bin/clang++"] [profile.Debug] diff --git a/tests/test.rs b/tests/test.rs index 5328321..b5d3517 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -54,7 +54,8 @@ fn test_01() { let test_one = project; println!("test_one: {:?}", *test_one); assert_eq!(test_one.executables.len(), 1); - assert_eq!(test_one.static_libraries.len(), 1); + assert_eq!(test_one.static_libraries.len(), 0); + assert_eq!(test_one.shared_libraries.len(), 1); let exe = test_one.executables.first().unwrap(); assert_eq!(exe.name, "myexe"); @@ -67,7 +68,7 @@ fn test_01() { assert_eq!(exe.links[3].name(), "nasmobjs"); assert_eq!(exe.links[4].name(), "zstd"); - let lib = test_one.static_libraries.first().unwrap(); + let lib = test_one.shared_libraries.first().unwrap(); assert_eq!(lib.name, "mylib"); assert_eq!(lib.sources.cpp.len(), 1); assert_eq!(lib.sources.cpp[0].full, cwd.join("mylib.cpp"));