Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift on Linux - importing third-party modules

I am writing some entry-level swift code on Linux as a learning exercise.

As a general task, I wish to make use of a third-party Swift module in my own code. Let's call this module "Foo". The Foo module has a Package.swift file, and after running swift build in that directory, it has created .build/debug/libFoo.so.

Now I wish to do two things with this:

  1. Be able to import Foo in the REPL.
  2. Be able to import Foo in my own swift program, perhaps by linking with this shared object.

I have a feeling both tasks are related, so for now they are in the same question.

For 1., I don't understand how Packages become 'findable' by the REPL. I tried swift -F .build/debug -framework Foo but I get the "no such module" error. I also tried swift -I .build/debug with the same result.

For 2., I examined swiftc --help and there are -L and -l options however I was not able to find the right way to use these:

$ swiftc main.swift -L ../foo.git/.build/debug -llibFoo.so
main.swift:1:8: error: no such module 'Foo'
import Foo
       ^

I'm using both/either Swift 2.2 or 3.0 (used swim rather than swift build for 2.2 as there is no swift build - but it produces the same output I believe).

Note that I understand swift build can automagically download and build a third-party module however I'd like to know how to incorporate on-disk modules as they may be my own work-in-progress modules.


EDIT: I tried a little experiment with swift3 based on a discovery that you can use local paths as the url: parameter in the Package's dependencies: list, at least for local development.

I created a directory Bar and Bar/Package.swift:

import PackageDescription
let package = Package(name: "Bar")

I also created Bar/Sources/bar.swift containing:

public func bar(arg: Int) -> Int {
    return arg * 2
}

The intention is that module Bar provides the function called bar(arg:).

I did a git init, git add ., git commit -m "Initial commit." and then git tag 1.0.0 to create a tagged local git repo for this module.

Then back at the top level I created directory Foo and Foo/Package.swift:

import PackageDescription
let package = Package(
    name: "Foo",
    dependencies: [ .Package(url: "../Bar", majorVersion: 1) ]
)

Note the relative path for ../Bar.

I also created Foo/Sources/main.swift:

import Bar
print(bar(arg: 11))

Now when I swift build inside Foo, it clones Bar and builds it. However then I get the following error; no such module:

$ swift build
Compile Swift Module 'Bar' (1 sources)
Compile Swift Module 'Foo' (1 sources)
.../Foo/Sources/main.swift:1:8: error: no such module 'Bar'
import Bar
       ^
<unknown>:0: error: build had 1 command failures
error: exit(1): .../swift-3.0-PREVIEW-4-ubuntu14.04/usr/bin/swift-build-tool -f .../Foo/.build/debug.yaml

Oddly, if I do the exact same build command again, I get a different error:

$ swift build
Compile Swift Module 'Foo' (1 sources)
Linking .build/debug/Bar
.../Foo/Sources/main.swift:3:7: error: use of unresolved identifier 'bar'
print(bar(arg: 11))
      ^~~
<unknown>:0: error: build had 1 command failures
error: exit(1): .../swift-3.0-PREVIEW-4-ubuntu14.04/usr/bin/swift-build-tool -f .../Foo/.build/debug.yaml

I had hoped that this might work.

like image 372
davidA Avatar asked Aug 22 '16 04:08

davidA


Video Answer


2 Answers

Be able to import Foo in my own swift program, perhaps by linking with this shared object.

Using the example you posted in your question after "EDIT," this seems to work fine provided you use swift build. The Swift Package Manager will handle all of the dependencies for you, even if they're on-disk (this works on Swift 3 and 4):

$ cd Foo

$ swift build
Cloning /path/to/Bar
HEAD is now at 0c3fd6e Initial commit.
Resolved version: 1.0.0
Compile Swift Module 'Bar' (1 sources)
Compile Swift Module 'Foo' (1 sources)
Linking ./.build/debug/Foo

$ .build/debug/Foo
22

Note that Foo/.build/debug doesn't contain any .so files:

$ ls Foo/.build/debug
Bar.build  Bar.swiftdoc  Bar.swiftmodule  Foo  Foo.build  Foo.swiftdoc  Foo.swiftmodule  ModuleCache

I believe the .swiftdoc and .swiftmodule files are used instead.

Be able to import Foo in the REPL.

This part's a bit messier, but I found the solution here. To apply it to your example, you have two options:

  • Use swift build with extra flags (this works on Swift 3 and 4):

    $ cd Bar
    
    $ swift build -Xswiftc -emit-library
    Compile Swift Module 'Bar' (1 sources)
    
    $ swift -I .build/debug -L . -lBar
      1> import Bar
      2> bar(arg: 11) 
    $R0: Int = 22
      3> 
    

    This creates libBar.so in the current directory:

    $ ls
    libBar.so  Package.swift  Sources
    


  • Update your Package.manifest (this is specific to Swift 4):

    1. The updated Package.manifest would look something like this:

      // swift-tools-version:4.0
      import PackageDescription
      
      let package = Package(
          name: "Bar",
          products: [
              .library(
                  name: "Bar",
                  type: .dynamic,
                  targets: ["Bar"]),
          ],
          targets: [
              .target(
                  name: "Bar",
                  dependencies: [],
                  path: "Sources"),
          ]
      )
      
    2. And this is how you do the build and call the REPL:

      $ cd Bar
      
      $ swift build
      Compile Swift Module 'Bar' (1 sources)
      Linking ./.build/x86_64-unknown-linux/debug/libBar.so
      
      $ swift -I .build/debug -L .build/debug -lBar
        1> import Bar 
        2> bar(arg: 11) 
      $R0: Int = 22
        3>  
      

    This creates libBar.so in the .build/debug directory:

    $ ls .build/debug
    Bar.build  Bar.swiftdoc  Bar.swiftmodule  libBar.so  ModuleCache
    

If you're unable to reproduce these results, I would suggest cleaning out any .build directories and .so files, and installing a clean version of Swift (I recommend swiftenv for this).

like image 53
bmaupin Avatar answered Nov 11 '22 23:11

bmaupin


As stated in the documentation, Swift on Linux with its Package Manager is work in progress, so no wonder there are bugs and lack of information. However, here is what I found by experimenting and reading help.

If the module Foo has a library, libFoo.so, in /LibLocation and Foo.swiftmodule in /ModuleLocation, then it is possible to import and use Foo in a Swift program, call it main.swift, and then compile it by doing

swiftc -I /ModuleLocation -L /LibLocation -lFoo main.swift

One can also do it in REPL by launching it as

swift -I /ModuleLocation -L /LibLocation -lFoo

i.e. essentially giving it the same arguments as to swiftc. BTW, if the module was built using swift build, the ModuleLocation is likely the same as LibLocation.

As I mentioned in an earlier comment, your example with Foo and Bar, both built using swift build, worked for me just fine, so I could not reproduce the problem.

BTW, in addition to reading swift.org documentation and command line help, one can glean plenty of interesting and potentially useful information by running swift build and other commands with the -v flag. To find out about some hidden options available with swiftc do

swiftc -help-hidden
like image 34
Anatoli P Avatar answered Nov 12 '22 01:11

Anatoli P