Import swift file in script

Hello everyone,

If you search “import swift script” on Google this seems like a recurring question and people tend to do solve that by:

  1. Putting everything in the same file
  2. Using a script to concatenate all files into one single script

None of these is actually ideal.
So I’d like to propose add the ability to import a swift file from another swift file, only when in interpretation mode. Here’s a suggestion on the syntax:

import "A.swift" // only possible in interpretation mode. Module would automatically be called A.

A.MyClass().myMethod()
3 Likes

Even better, if import A would find either a linked module A or a local file A.swift.

3 Likes

How do you execute the script? I assume it’s some way of invoking the swift myfile.swift right? And if so, then it is not too hard to list all the necessary files in the invocation, is it? And it also has a benefit of specifying what files are needed (in a single place!) and their relative locations, as opposed to an import, that would require working out the details of where to look for the .swift files.

1 Like

Specifying all the files on command line is cumbersome and really not developer friendly.
Plus installing a (multiple files) script on your machine and importing it into your own script would be rather complicated.
As for the details of where to look for .swift files there a many possibilites:

  • require would use LOAD_PATH (or any other swift name) to where to look for .swift files
  • require may also work with relative paths, so if B.swift and C.swift are in the same directory one could import the other one.
1 Like

FWIW, it would be great to have a bigger, consistent story for Swift command-line use. I think Perl 5 has a lot of inspiration to offer in this case, with its -E one-liner wonder machine or -p and -n implicit loops; see man perlrun. If we collected a bunch of command-line use cases, we would have more data to design the import feature, too.

There was some discussion about this in this thread:

In my opinion the most promising development is SPM's (internal) Utility module mentioned in the thread.

1 Like

Would this mean one could also import a Swift file located deeper or outside the folder? I.e.:

import "./foo/bar.swift"
import "./../bar/foo.swift"

or simply

import foo/bar
import ../bar/foo

I feel import is taken to mean import a module, and in this context you’re not really importing a “module”, but a file. So, I feel like this kind of “import” would be better expressed as a #include compiler directive that would only function in REPL mode.

Another alternative would be something like import file "../other/myswift.swift" which is somewhat akin to our currently existing import class SomeModule.SomeClass.

4 Likes

You can already import modules from source in script mode today, using some not-really-documented flags. I discovered it digging through the interpreter test suites a while back:

other_module.swift

public func importedFunction() {
  print("Imported function was called!")
}

script.swift

#!/usr/bin/swift -frontend -interpret -enable-source-import -I.

import other_module  // this imports other_module.swift

importedFunction()

Note that you need to add the current directory (or whatever directory your “modules” live in) to the import path with -I.

$ chmod +x ./script.swift
$ ./script.swift  
Imported function was called!

So I guess the question becomes, is this something that should be elevated to a more supported/documented feature? It would certainly improve the state of Swift as an easy-to-use scripting language.

29 Likes

Wow amazing, thanks for this tip @allevato.
That should definitely be documented. Even though I think -enable-source-import -I. should be default behaviour when making scripts.

Maybe we could have a single switch (-script?) that would turn on reasonable defaults for this use case?

#!/usr/bin/env swift -script
…
4 Likes

It would also be very neat if we could have site-wide modules, that could be used from such scripts. Similar to global packages installed by python/pip. That way you could install some common tooling (libraries) and use them from such ad-hoc scripts without all the overhead of creating full-blown SwiftPM projects.

2 Likes

Please please please do not use -enable-source-import. It does not work. It’s barely good enough for the tests today and we are really hoping to take it out. (If you want concrete things to worry about, it may straight-up miscompile your code.)


Swift doesn’t have script file importing because Swift modules are usually more than one file, and we don’t want to have different conventions for scripts vs. larger packages, and because you’d probably want to cache the results of compiling each module anyway. Your script would just be very slow otherwise.

At some point we probably need to sit down and really consider what Swift scripting should look like. The current interpreter mode is good enough for things that fit in one file and that don’t need to be optimized, but as you’ve all noted it doesn’t really scale. But that’s a bigger effort than just allowing source imports.

What is possible today is compiling packages as libraries and then putting them in some common search path that your scripts find with -I /path/to/libs/. That lets you use optimized versions of the libraries as well. I don’t remember if the package manager makes it easy to get at the dylib and swiftmodule you’d need to do such a thing, though.

8 Likes

I do agree that having different conventions for scripts vs compiled code is not cool, but Swift does not have any notion of module outside of compiled code. It is not defined in the code, but in command line.

I think there are two subjects here:

  • performance
  • source import

The latter seems rather important to me (hence the topic) and I think we should resolve it before trying to do any performance optimization. By definition scripting is some kind of slow so I wouldn’t care about it right now.

Including compiled scripts is probably better for dependencies.
What if the script itself is made of multiple files though? Would that require to compile the script at every edit? I suppose you could make a compile&run script for doing that though…

Modules definitely do show up in code: they’re the extent of the internal (default) access control; they’re usable (usually) for disambiguation of top-level names (Swift.Sequence vs. MyKit.Sequence); they affect diagnostics as an approximation of what is and isn’t under your control; and, of course, they’re what you get today when you say import.

You’re totally right that not performing well isn’t a showstopper for enabling source import. But I think this is something that needs more design to fit in the Swift world than just “import this file”. For example, maybe import FooKit could pick up a directory named FooKit, perhaps a directory containing a Package.swift manifest. (And yes, since that’s complicated, maybe it would work for a single file as well.)

So to be clear, I don’t want to quash the discussion here! Just the use of -enable-source-import. That pretty much only works for testing the type checker.

I was talking about current module, which is never declared inside the code, only by command line using -module-name.

Let’s say we don’t use -enable-source-import;)
What sort of import the user should be able to do from script file?
Importing a compiled library should obviously work, but IMO importing a scripting library (hence not compiled) and a single file should work too. Thoughts?

Is there an update around this? Looks like when I do the source-import it wants an SDK. I do agree having a import file "/path/to/file.swift" would be nice as scripting is going to grow with the number of macOS administrators looking at swift to provide native notifications.

Terms of Service

Privacy Policy

Cookie Policy