Skip to content

Instantly share code, notes, and snippets.

@robertjpayne
Last active April 23, 2017 15:40
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save robertjpayne/5dacd4d31299165c28ab to your computer and use it in GitHub Desktop.
Save robertjpayne/5dacd4d31299165c28ab to your computer and use it in GitHub Desktop.
A ruby function to generate a static library + clang module from a single Swift source file
require "Subprocess"
require "tmpdir"
#
# Currently will only convert a single swift code file into a static library
# and cannot include any Objective-C code.
#
# Usage: generate("/path/to/MyCode.swift", :ios)
#
def generate(file, platform, dst=nil)
name = File.basename(file).split(".")[0]
build_path = Dir.mktmpdir
output_path = (dst) ? File.join(dst, name) : File.expand_path("../#{name}", __FILE__)
sdks = []
target = ""
if platform == :ios
sdks = {
"iphoneos" => {"armv7" => "arm", "arm64" => "arm64"},
"iphonesimulator" => {"i386" => "i386", "x86_64" => "x86_64"}
}
target = "apple-ios7.0"
elsif platform == :osx
sdks = {
"macosx" => {"i386" => "i386", "x86_64" => "x86_64"}
}
target = "apple-macosx10.10"
end
FileUtils.rm_r(build_path) if File.exists?(build_path)
Dir.mkdir(build_path)
return if sdks.length == 0
sdks.each do |sdk, archs|
sdk_path = Subprocess.check_output(["xcrun", "--show-sdk-path", "--sdk", sdk]).chop
archs.each do |arch_name, arch_output_name|
puts "----> Generating files for #{arch_name}"
module_path = File.join(build_path, "#{arch_output_name}.swiftmodule")
module_doc_path = File.join(build_path, "#{arch_output_name}.swiftdoc")
dylib_path = File.join(build_path, "#{arch_output_name}.dylib")
object_path = File.join(build_path, "#{arch_output_name}.o")
header_path = File.join(build_path, "#{name}.h")
Subprocess.check_output([
"xcrun",
"swiftc",
"-sdk", sdk_path,
"-target", "#{arch_name}-#{target}",
"-emit-objc-header",
"-emit-module",
"-emit-library",
"-module-name", "#{name}",
"-module-link-name", "swift#{name}",
"-g",
"-j8",
"-O",
"-o", dylib_path,
file
])
Subprocess.check_output([
"xcrun",
"swiftc",
"-sdk", sdk_path,
"-target", "#{arch_name}-#{target}",
"-module-name", "#{name}",
"-parse-as-library",
"-g",
"-j8",
"-o", object_path,
"-O",
"-c", file
])
Subprocess.check_output([
"libtool",
"-static",
object_path,
"-o",
File.join(build_path, "#{arch_output_name}.a"),
])
FileUtils.mv(File.join(build_path, "#{name}.swiftmodule"), module_path)
FileUtils.mv(File.join(build_path, "#{name}.swiftdoc"), module_doc_path)
end
end
puts "----> Packaging"
FileUtils.rm_r(output_path) if File.exists?(output_path)
Dir.mkdir(output_path)
Dir.mkdir(File.join(output_path, "Headers"))
Dir.mkdir(File.join(output_path, "#{name}.swiftmodule"))
Dir["#{build_path}/*.h"].each do |f|
FileUtils.cp(f, File.join(output_path, "Headers", File.basename(f)))
end
Dir["#{build_path}/*.swiftmodule"].each do |f|
FileUtils.cp(f, File.join(output_path, "#{name}.swiftmodule", File.basename(f)))
end
Dir["#{build_path}/*.swiftdoc"].each do |f|
FileUtils.cp(f, File.join(output_path, "#{name}.swiftmodule", File.basename(f)))
end
File.open(File.join(output_path, "module.modulemap"), "w+") do |fh|
fh << """framework module #{name} {
header \"#{name}.h\"
export *
}"""
end
args = [
"lipo",
"-create"
]
Dir["#{build_path}/*.a"].each do |archive|
args << archive
end
args += ["-o", File.join(output_path, "libswift#{name}.a")]
Subprocess.check_output(args)
puts "----> Finished '#{output_path}'"
rescue => e
puts "----> Failed #{e.message}"
ensure
FileUtils.rm_r(build_path)
end
@robertjpayne
Copy link
Author

The usage of this should be something like:

generate("Alamofire.swift", :ios, "Modules/")

It will output a directory with a structure like:

Alamofire/
    Alamofire.swiftmodule/
        <arch>.swiftmodule
        <arch>.swiftdoc
    Headers/
        Alamofire.h
    libswiftAlamofire.a
    module.modulemap

To include this module in your project find the Xcode build settings under Swift Compiler - Search Paths there should be an Import Paths. With this Import Paths add your Modules/ directory and set it to recursive.

Once that is done you should be able to use import Alamofire and be smooth sailing.

@NSURLSession0
Copy link

For tvOS support:

elsif platform == :tvos
    sdks = {
      "appletvos" => {"arm64" => "arm64"}
    }
    target = "apple-tvos9.0"
  end

@mcconkiee
Copy link

can I use the static result as an objective c dependency? I'd love to bring some swift code to an objective c library I am building, but last i heard, that's not possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment