diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bb08710a..33e5115d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,18 +77,6 @@ jobs: command: check args: --verbose --no-default-features - - name: Install GNUStep libobjc2 - if: contains(matrix.platform.os, 'ubuntu') - run: | - wget https://github.com/gnustep/libobjc2/archive/refs/tags/v1.9.tar.gz - tar -xzf v1.9.tar.gz - mkdir libobjc2-1.9/build - cd libobjc2-1.9/build - export CC="clang" - export CXX="clang++" - cmake ../ - sudo make install - - name: Install GNUStep make if: contains(matrix.platform.os, 'ubuntu') run: | @@ -122,7 +110,7 @@ jobs: with: command: test # Temporary fix - args: --verbose --no-fail-fast --no-default-features --package objc2_sys --package objc2 --package objc2_encode --package objc2_foundation + args: --verbose --no-fail-fast --no-default-features --features objc2_sys/vendor_gnustep --package objc2_sys --package objc2 --package objc2_encode --package objc2_foundation - name: Test if: ${{ !contains(matrix.platform.os, 'ubuntu') }} diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..65a5effb4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,7 @@ +[submodule "libobjc2"] + path = gnustep_libobjc2_src/libobjc2 + url = https://github.com/gnustep/libobjc2.git + branch = 2.1 +[submodule "gnustep-base"] + path = gnustep_base_src/libs-base + url = https://github.com/gnustep/libs-base.git diff --git a/Cargo.toml b/Cargo.toml index cdd936995..4e9515f30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,7 @@ [workspace] members = [ + "gnustep_libobjc2_src", + "gnustep_base_src", "objc2", "objc2_block", "objc2_encode", diff --git a/README.md b/README.md index f58da4de5..44fad94ee 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,10 @@ [![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt) [![CI Status](https://github.com/madsmtm/objc2/workflows/CI/badge.svg)](https://github.com/madsmtm/objc2/actions) + +## Submodules + +This project uses `git` submodules for vendored dependencies, remember to update them after pulling: +```sh +$ git submodule update --init --recursive +``` diff --git a/gnustep_base_src/Cargo.toml b/gnustep_base_src/Cargo.toml new file mode 100644 index 000000000..a1f54b845 --- /dev/null +++ b/gnustep_base_src/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "gnustep_base_src" +version = "0.0.1" +authors = ["Mads Marquart "] +edition = "2018" + +description = "Source of The GNUstep Base Library and logic to build it" +keywords = ["gnustep", "objc", "gnustep-base", "build-dependencies"] +categories = [ + "development-tools::build-utils", +] +readme = "README.md" +repository = "https://github.com/madsmtm/gnustep_base_src" +documentation = "https://docs.rs/gnustep_base_src/" +license = "MIT/Apache-2.0" diff --git a/gnustep_base_src/libs-base b/gnustep_base_src/libs-base new file mode 160000 index 000000000..326dc210a --- /dev/null +++ b/gnustep_base_src/libs-base @@ -0,0 +1 @@ +Subproject commit 326dc210a4fca396489d6527d4649d61dff4bed5 diff --git a/gnustep_base_src/src/lib.rs b/gnustep_base_src/src/lib.rs new file mode 100644 index 000000000..04a3a31d6 --- /dev/null +++ b/gnustep_base_src/src/lib.rs @@ -0,0 +1,99 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +const NO_SOURCES_MSG: &'static str = r#" +GNUStep Base sources not present, please run: + +$ git submodule update --init --recursive + +If you did not checkout via git, you will +need to fetch the submodule's contents from +https://github.com/gnustep/libs-base +"#; + +pub struct Builder {} + +impl Builder { + pub fn new() -> Self { + Self {} + } + + pub fn build(&mut self) -> Artifacts { + // GNUStep only compiles with clang, so try that first. + // (But let the user specify a different path if they need to). + if env::var_os("CC").is_none() { + env::set_var("CC", "clang"); + } + if env::var_os("CXX").is_none() { + env::set_var("CXX", "clang++"); + } + + let source_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("libs-base"); + if !source_dir.join("Headers/Foundation/Foundation.h").exists() { + panic!("{}", NO_SOURCES_MSG); + } + + let mut configure = Command::new(source_dir.join("configure")); + // .arg("--with-config-file=") + // .arg("--enable-libffi") + // .arg("--with-default-config=") + // .arg("--prefix") // Or GNUSTEP_SYSTEM_ROOT ? + + // run `make` and `make install` + + let dst: PathBuf = todo!(); + + Artifacts { + source_dir, + include_dir: dst.join("include"), + lib_dir: dst.join("lib"), + lib_name: "gnustep-base", + } + } +} + +pub struct Artifacts { + source_dir: PathBuf, + include_dir: PathBuf, + lib_dir: PathBuf, + lib_name: &'static str, +} + +impl Artifacts { + pub fn include_dir(&self) -> &Path { + &self.include_dir + } + + pub fn lib_dir(&self) -> &Path { + &self.lib_dir + } + + pub fn lib_name(&self) -> &str { + &self.lib_name + } + + pub fn print_cargo_metadata(&self) { + println!("cargo:rustc-link-search=native={}", self.lib_dir.display()); + println!("cargo:rustc-link-lib=dylib={}", self.lib_name); + println!("cargo:include={}", self.include_dir.display()); + println!("cargo:lib={}", self.lib_dir.display()); + } + + pub fn print_cargo_rerun_if_changed(&self) { + println!("cargo:rerun-if-env-changed=CC"); + println!("cargo:rerun-if-env-changed=CXX"); + rerun_if(&self.source_dir); + } +} + +// https://github.com/rust-lang/git2-rs/blob/0.13.23/libgit2-sys/build.rs#L228-L236 +fn rerun_if(path: &Path) { + if path.is_dir() { + for entry in std::fs::read_dir(path).expect("read_dir") { + rerun_if(&entry.expect("entry").path()); + } + } else { + println!("cargo:rerun-if-changed={}", path.display()); + } +} diff --git a/gnustep_libobjc2_src/Cargo.toml b/gnustep_libobjc2_src/Cargo.toml new file mode 100644 index 000000000..f9f3052aa --- /dev/null +++ b/gnustep_libobjc2_src/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "gnustep_libobjc2_src" +version = "0.0.1" +authors = ["Mads Marquart "] +edition = "2018" + +description = "Source of GNUStep's libobjc2 and logic to build it" +keywords = ["gnustep", "objc", "libobjc2", "build-dependencies"] +categories = [ + "development-tools::build-utils", +] +readme = "README.md" +repository = "https://github.com/madsmtm/gnustep_libobjc2_src" +documentation = "https://docs.rs/gnustep_libobjc2_src/" +license = "MIT/Apache-2.0" + +exclude = [ + "libobjc2/ABIDoc/*", + "libobjc2/Test/*", + "libobjc2/third_party/robin-map/tests/*", +] + +[dependencies] +cmake = "0.1.45" diff --git a/gnustep_libobjc2_src/README.md b/gnustep_libobjc2_src/README.md new file mode 100644 index 000000000..941353a70 --- /dev/null +++ b/gnustep_libobjc2_src/README.md @@ -0,0 +1,9 @@ +# `gnustep_libobjc2_src` + +Source of GNUStep's libobjc2 and logic to build it for Rust. + +You probably want to use `objc2_sys`, but this is kept separate from that so +that users don't download the entire GNUStep source when they don't need to. + +Using `clang` with at least version `8.0.0` is recommended. You can specify +your desired compiler using the `CC` and `CXX` environment variables. diff --git a/gnustep_libobjc2_src/libobjc2 b/gnustep_libobjc2_src/libobjc2 new file mode 160000 index 000000000..f648903db --- /dev/null +++ b/gnustep_libobjc2_src/libobjc2 @@ -0,0 +1 @@ +Subproject commit f648903dbd6835c84519bc3f23a41efd3aee216b diff --git a/gnustep_libobjc2_src/src/lib.rs b/gnustep_libobjc2_src/src/lib.rs new file mode 100644 index 000000000..feefa9c76 --- /dev/null +++ b/gnustep_libobjc2_src/src/lib.rs @@ -0,0 +1,178 @@ +//! TODO +//! +//! See +//! - https://github.com/gnustep/libs-base/blob/base-1_28_0/INSTALL +//! - https://github.com/blas-lapack-rs/blis-src/blob/main/blis-src/build.rs +//! - https://github.com/alexcrichton/openssl-src-rs/blob/master/src/lib.rs +//! - https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.70/autoconf.html#Running-configure-Scripts +//! +//! And gnustep-make would also have to be installed: +//! - https://github.com/gnustep/tools-make/blob/master/Documentation/gnustep-make.texi +//! - http://www.gnustep.org/resources/documentation/Developer/Make/Manual/DESIGN + +use std::env; +use std::path::{Path, PathBuf}; + +const NO_SOURCES_MSG: &'static str = r#" +libobjc2 sources not present, please run: + +$ git submodule update --init --recursive + +If you did not checkout via git, you will +need to fetch the submodule's contents from +https://github.com/gnustep/libobjc2 +"#; + +pub struct Builder { + lib_kind: LibKind, + objcxx: bool, +} + +impl Builder { + pub fn new() -> Self { + Self { + lib_kind: LibKind::Dynamic, + objcxx: true, + } + } + + /// Set the type of library to be built, and how linking is performed. + /// + /// Possible options are [`LibKind::Static`] and [`LibKind::Dynamic`]. + /// + /// Defaults to [`LibKind::Dynamic`]. + pub fn lib_kind(&mut self, kind: LibKind) -> &mut Self { + self.lib_kind = kind; + self + } + + /// Enable support for Objective-C++. + /// + /// Namely interoperability between Objective-C and C++ exceptions. + /// + /// Defaults to [`true`]. + pub fn objcxx(&mut self, objcxx: bool) -> &mut Self { + self.objcxx = objcxx; + self + } + + pub fn build(&mut self) -> Artifacts { + // GNUStep only compiles with clang, so try that first. + // (But let the user specify a different path if they need to). + if env::var_os("CC").is_none() { + env::set_var("CC", "clang"); + } + if env::var_os("CXX").is_none() { + env::set_var("CXX", "clang++"); + } + + let source_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("libobjc2"); + if !source_dir.join("objc/objc.h").exists() { + panic!("{}", NO_SOURCES_MSG); + } + + let dst = cmake::Config::new(&source_dir) + // Default to ignoring `gnustep-config` presence, since they + // usually want to install the libraries globally (which requires + // root). Users that want systemwide installation should just + // install it themselves, and shouldn't need to vendor GNUStep. + .define("GNUSTEP_INSTALL_TYPE", "NONE") + // Don't bother building tests, we're not gonna run them anyways + // (and they're not available when packaged, see Cargo.toml). + .define("TESTS", "OFF") + // Having this on also builds the dynamic library, but not really + // anything we can do to change that. + .define( + "BUILD_STATIC_LIBOBJC", + match self.lib_kind { + LibKind::Static => "ON", + LibKind::Dynamic => "OFF", + }, + ) + .define("ENABLE_OBJCXX", if self.objcxx { "ON" } else { "OFF" }) + // Various other defaults + // .define("OLDABI_COMPAT", "ON") + // .define("DEBUG_ARC_COMPAT", "OFF") + // .define("ENABLE_TRACING", "OFF") + // .define("LEGACY_COMPAT", "OFF") + // .define("LIBOBJC_NAME", "objc") + // .define("TYPE_DEPENDENT_DISPATCH", "ON") + // .define("STRICT_APPLE_COMPATIBILITY", "0") // Default none + // TODO: .static_crt(?) + .build_target("install") + .build(); + + Artifacts { + source_dir, + include_dir: dst.join("include"), + lib_dir: dst.join("lib"), + lib_kind: self.lib_kind, + lib_name: "objc", + } + } +} + +pub struct Artifacts { + source_dir: PathBuf, + include_dir: PathBuf, + lib_dir: PathBuf, + lib_kind: LibKind, + lib_name: &'static str, +} + +impl Artifacts { + pub fn source_dir(&self) -> &Path { + &self.source_dir + } + + pub fn include_dir(&self) -> &Path { + &self.include_dir + } + + pub fn lib_dir(&self) -> &Path { + &self.lib_dir + } + + pub fn lib_kind(&self) -> LibKind { + self.lib_kind + } + + pub fn lib_name(&self) -> &str { + &self.lib_name + } + + pub fn print_cargo_metadata(&self) { + let kind = match self.lib_kind { + LibKind::Dynamic => "dylib", + LibKind::Static => "static", + }; + println!("cargo:rustc-link-search=native={}", self.lib_dir.display()); + println!("cargo:rustc-link-lib={}={}", kind, self.lib_name); + println!("cargo:include={}", self.include_dir.display()); + println!("cargo:lib={}", self.lib_dir.display()); + } + + pub fn print_cargo_rerun_if_changed(&self) { + println!("cargo:rerun-if-env-changed=CC"); + println!("cargo:rerun-if-env-changed=CXX"); + rerun_if(&self.source_dir); + } +} + +fn rerun_if(path: &Path) { + if path.is_dir() { + for entry in std::fs::read_dir(path).expect("read_dir") { + rerun_if(&entry.expect("entry").path()); + } + } else { + println!("cargo:rerun-if-changed={}", path.display()); + } +} + +#[non_exhaustive] +#[derive(Clone, Copy)] +pub enum LibKind { + Dynamic, + Static, + // Framework, +} diff --git a/objc2_sys/Cargo.toml b/objc2_sys/Cargo.toml index 88f897d5d..feafe2cb6 100644 --- a/objc2_sys/Cargo.toml +++ b/objc2_sys/Cargo.toml @@ -27,3 +27,15 @@ readme = "README.md" # See https://doc.rust-lang.org/cargo/reference/build-scripts.html#overriding-build-scripts links = "objc" build = "build.rs" + +[features] +# If enabled, GNUStep's libobjc2 will be compiled and linked from source. +vendor_gnustep = ["gnustep_libobjc2_src"] +# Whether to link to libobjc statically. +# +# Only supported for vendored dependencies, it will trigger a compile-time +# error when venoring is not enabled. +static = [] + +[build-dependencies] +gnustep_libobjc2_src = { path = "../gnustep_libobjc2_src", optional = true } diff --git a/objc2_sys/build.rs b/objc2_sys/build.rs index c2db6ca95..0f13dbd45 100644 --- a/objc2_sys/build.rs +++ b/objc2_sys/build.rs @@ -1,23 +1,41 @@ -use std::env; +#[cfg(feature = "vendor_gnustep")] +fn main() { + let mut builder = gnustep_libobjc2_src::Builder::new(); + + #[cfg(feature = "static")] + builder.lib_kind(gnustep_libobjc2_src::LibKind::Static); + #[cfg(not(feature = "static"))] + builder.lib_kind(gnustep_libobjc2_src::LibKind::Dynamic); + + let artifacts = builder.build(); + artifacts.print_cargo_metadata(); + artifacts.print_cargo_rerun_if_changed(); + // Add #[cfg(gnustep)] directive + println!("cargo:rustc-cfg=gnustep"); +} + +#[cfg(not(feature = "vendor_gnustep"))] fn main() { + #[cfg(feature = "static")] + compile_error!("Can only link statically to libobjc when vendoring is enabled."); + // Only rerun if this file changes; the script doesn't depend on our code println!("cargo:rerun-if-changed=build.rs"); - // Link to libobjc - println!("cargo:rustc-link-lib=dylib=objc"); - let target_vendor = env::var("CARGO_CFG_TARGET_VENDOR").unwrap(); + let target_vendor = std::env::var("CARGO_CFG_TARGET_VENDOR").unwrap(); - // Adds useful #[cfg(apple)] and #[cfg(gnustep)] directives if target_vendor == "apple" { + // Add #[cfg(apple)] directive println!("cargo:rustc-cfg=apple"); } else { // TODO: Are there other possibilities than GNUStep? Airyx? Is it // possible to use Apple's open source objc4 on other platforms? + + // Add #[cfg(gnustep)] directive println!("cargo:rustc-cfg=gnustep"); - // TODO: Should we vendor GNUStep's libobjc2? - // Using Cargo.toml: - // [target.'cfg(not(target_vendor = "apple"))'.build-dependencies] - // cc = "1.0" } + + // Link to libobjc + println!("cargo:rustc-link-lib=dylib=objc"); }