Swift Package and `#if canImport(...)`. How does it work? - swift

Excuse the vague title.
I'm trying to build a package to help me use third-party cloud storage APIs (Firebase Storage for example), adding Combine support, etc. This package does the same thing with CloudKit. Everything compiles fine, but when I import the package module into a separate project of mine, the module is apparently missing some public symbols...
Specifically, the ones wrapped inside of an #if canImport(FirebaseStorage) condition. Since Firebase doesn't support SwiftPM yet, this part of the package behaves as expected in the package project itself; it simply skips compiling that whole bit. I figured that a client project that can import this module would compile it fine.
Aside: What I'm trying to do looks something like optional dependencies. I don't want to have to import Firebase to use this package's other features. I have considered splitting the package into separate sub-packages, each depending upon the particular third-party library I want to use. I might do that anyway. But the problem remains that Firebase doesn't yet support SwiftPM (although I hear they're close).
My issue appears similar to this one. My client project just doesn't seem to see the conditioned symbols, though it can import Firebase and FirebaseStorage just fine! I mean that the generated module header is missing them entirely, preventing my client project from compiling when I use them.
It seems to me that the compile condition never leaves the package's own scope of dependent targets. Is this the case? Or am I missing something obvious? I had always assumed that Swift Packages just import and compile the Swift source files into named modules, but now I think that is not so.
Is there a way to build code into a Swift Package that compiles only when the client can import a third-party module that does not yet support SwiftPM? Or does conditional compilation not work that way?
EDIT: Here is the Swift documentation on conditional compilation, for reference.

(Answer from experience in Apr 2020)
It looks like I'm just misunderstanding the compile order.
Importing my packaged module (let's call it CloudStorage) declares a dependency in the client project to that module. Before the client project can compile with its other dependencies, CloudStorage needs to compile without the main project's dependencies. Since CloudStorage doesn't know anything about those dependencies, canImport for those dependencies evaluates to false.
This may have changed in a later version of Swift. I've yet to try again.

canImport checks whether the module name provided can be imported. For swift packages, this evaluates to true if you have provided the module as a target dependency in the package manifest and all of its associated target dependency condition are satisfied.
The benefit of this is this allows you to write code in a platform agnostic way and let your package manifest take care of platform support.
Let's say I have Firebase as a dependency in my target:
.product(name: "Firebase", package: "firebase-ios-sdk", condition: .when(platforms: [.iOS, .macOS, .tvOS])),
I can write my code that depends on Firebase with canImport. Suppose in future firebase-sdk started supporting another platform, I can add the platform to my availability condition and start supporting that platform in my code as well.
But if you don't have the module listed as dependency, even if your client app can import the module, this condition will always evaluate to false. This is due to the sandbox nature of swift package build system and all your package targets are isolated so that consuming client's build settings doesn't affect your package target.

Related

Breakpoints ignored in package unit test

Breakpoints set in the testable code are ignored during unit tests. In the test schemes, debug executable is checked.
What might I try to use breakpoints?
Package:
Swift 5.3 Swift Package Manager
Perhaps irrelevant, but a symlink is used to share code between two libraries: one with logic and the other extending XCTest with custom assertions for that logic. This was the cleanest-for-client workaround to the problems Xcode has in importing packages that extend XCTest.

How do I use Swift Package Manager with an existing macOS project?

I've been using Cocoapods to manage dependencies for my Swift project. I came across this package which is not listed at Cocoapods. Instead it suggests using Swift Package Manager. However, whenever I try to use Swift Package Manager to do basically anything, it ends up completely destroying my entire project.
So, in order to figure out how to actually use Swift Package Manager, I'm playing with it in a test project.
Here's what I've tried:
Create a new project from Xcode
File -> New -> Project -> Cocoa App
Product Name: basic-ssh-test
This creates a basic application which loads a blank window when I hit "Run". Just for fun I added print("test") to the applicationDidFinishLaunching function in my AppDelegate so the debug window says "test" when I run the program.
Now I want to add the Shout package as a dependency.
swift package init --type executable
This creates the following files:
Creating executable package: basic-ssh-test
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/basic-ssh-test/main.swift
Creating Tests/
Now I'm going to add the "Shout" dependency to my new Package.swift file:
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "basic-ssh-test",
dependencies: [
.package(url: "https://github.com/jakeheis/Shout", from: "0.2.0")
],
targets: [
.target(
name: "basic-ssh-test",
dependencies: ["Shout"]),
]
)
And I'll pull in the dependencies:
swift package resolve
This results in the dependencies being pulled into the .build directory:
Fetching https://github.com/jakeheis/Shout
Fetching https://github.com/jakeheis/CSSH
Fetching https://github.com/IBM-Swift/BlueSocket
Cloning https://github.com/IBM-Swift/BlueSocket
Resolving https://github.com/IBM-Swift/BlueSocket at 0.12.91
Cloning https://github.com/jakeheis/CSSH
Resolving https://github.com/jakeheis/CSSH at 1.0.3
Cloning https://github.com/jakeheis/Shout
Resolving https://github.com/jakeheis/Shout at 0.3.0
Now I regenerate the xcodeproj file:
swift package generate-xcodeproj
Now when I open the xcodeproj file there is a new group called Sources that has a single directory basic-ssh-test with a single swift file in it main.swift with print("Hello, world!").
The code that I'm actually interested in running is now in a blue folder called basic-ssh-test. All of the necessary files are still in there, but instead of running my application, Xcode is running the main.swift file. I can tell because the debug output is "Hello, world!" instead of "test".
I've read a couple of tutorials that claim that Swift Package Manager will move my source files and continue to build the same as before, but that's clearly not the case.
There's also no longer a "Main Interface" option in my Build Settings, so I can't select "MainMenu.xib" as my application starting point.
This is essentially the same thing that happens when I try to use Swift Project Manager with my existing project. It pulls in the dependencies, but it basically ignores my entire existing project and just runs the "hello world" code.
How do I use Swift Package Manager to add a dependency to an existing project without ignoring the existing codebase?
I think SPM is only compatible with Mac command line executables or libraries. This explicitly states SPM doesn't support iOS, watchOS, or tvOS platforms at all. But, since macOS AppKit/Cocoa application targets are very similar to iOS or tvOS Xcode targets, I would say this statement implies that SPM can't be used with macOS Cocoa applications out of the box either, which is what I think you're hoping for.
It looks like there is some work here on how to use it with iOS application targets which should largely translate to a macOS Cocoa target.
Until the time of this writing answer, Swift Package Manager do not support the iOS, tvOS, and watchOS. Instead, you will have to add the files from the package directly to your project.
I would suggest you creating a Dependencies group in your project and a group below that with the package name, like this answer:
So, first you will add the files dependency that you want to import into your project to the name of the package name that included in Dependencies group that we make before.
After you add the files then you can access the code as you usually would when you write it yourself like the image below. No need imports. You can see more details in here. hope it helps.
You can create a windowed application using SPM (I have a whole project sitting waiting for ABI that does this) you just need some boiler plate in swift.main. I’ll dig it out.
However the method others are suggesting is never going to work (with current versions of SPM).
However, what I am seeing is that you really want to use SPM to manage dependencies (keeping up to date etc). So I think you could do that much more easily.
Rather than creating a executable as your default target, instead create a library (perhaps call it basic-ssh-dependencies). Generate your Xcode project and drag THAT into your primary Xcode project and configure your target to to depend on it. When you update the library from SPM for changes in dependencies you should re-gen your Xcode-proj.
Let me know your mileage and any wrinkles, or if I’ve misunderstood what you are trying to achieve.
Update for XCode 11
This is A LOT easier now with XCode 11. As of this writing the current version of XCode is 11.3.1.
For an existing XCode project simply:
go to File->Swift Packages->Add Package Dependency...
enter the URL of the hosted package you want to add
choose the rules about which versions your app will use
click Finish

Ada Encapsulated Library Project GNAT

I'm trying to use GNAT 95 to compile a program on my computer running CENTOS 7 64-bit with the GNAT-GPL 2015 bundle. I get a compiler message that looks like this:
gprbuild: encapsulated library projects not supported on this platform
My GPR file looks like this:
with "bc.gpr"; -- Pull in the booch95 components since ada95 doesnt have collections
with "mylibrary.gpr"; -- one of my library projects
library project Registry is
for Source_Dirs use ("src/**");
for Object_Dir use "bin";
for Library_Name use "registry";
for Library_Standalone use "encapsulated";
for Library_ALI_Dir use "lib/registry";
for Library_Dir use "plugins";
for Library_Kind use "dynamic";
for Library_Interfaces use ("...");
package Compiler is
for Default_Switches ("Ada") use ("-g", "-gnat95");
end Compiler;
package Linker is
for Linker_Options use ("-ldl", "-lgcov");
end Linker;
end project;
The Booch Components library is a static library project. Removing the encapsulated causes Ada to throw another error about trying to mix static and dynamic libraries:
shared library project "registry" cannot import static library project "bc"
Any Idea what might be causing the Ada compiler to crash?
Unless you’re using a very old version of the BCs, you can build as a shared or dynamic library by setting the scenario variable LIBRARY_TYPE to relocatable; either by setting as an environment variable, or
gprbuild -XLIBRARY_TYPE=relocatable ...
or with GPS. I’m not a GPS user, but having investigated it seems (with GPS GPL 2014) you select Scenario in the tabs on the left hand side, which should show the scenario variables in your project and the projects it depends on (bc in your case). Select the one you want to change and click the pen icon, you get a dialog box to update it.

Rust library development workflow

When developing a library in Rust (+ Cargo), how do I achieve the fast recompile/test cycle?
When developing an app, it's easy, I:
Make changes in the code
Switch to the terminal and run cargo run
See the compiler feedback
But now I want to extract parts of my app as a library and publish it on GitHub.
I would like to continue developing my app, but now with this library as a dependency. I'm going to develop both the library and the app in parallel.
How do I get same quick feedback now?
Both the library and the app will be developed on the same machine, I would like to make changes to the library, update the app correspondingly and see the compiler feedback.
I'm guessing I could use my library as a dependency in Cargo.toml and run cargo update each time I want to update my app's dependencies, but this will be somewhat slow because it will have to download the code from github each time and recompile all dependencies.
You can use this somewhat undocumented feature of cargo. Add the following line to ~/.cargo/config file (or /path/to/your/binary/project/.cargo/config to limit the effect to your binary project):
paths = ["/path/to/your/library"]
From now on every cargo package (or those under /path/to/your/binary/project root) which depends on your library will use /path/to/your/library as the source code for it regardless of what is specified in this package manifest, so you can keep Git repo URL in your program manifest. Hopefully this feature will be documented in future.
Update
This is now documented in the Cargo guide.

Scala import not working - object <name> is not a member of package, sbt preppends current package namespace in imports

I have an issue when trying to import in scala. The object Database exists under com.me.project.database but when I try to import it:
import com.me.project.database.Database
I get the error:
object Database is not a member of package com.me.project.controllers.com.me.project.database
Any ideas what the problem is?
Edit:
It is worth mentioning that the import is in the file Application.scala under the package com.me.project.controllers, I can't figure out why it would append the import to the current package though, weird...
Edit 2:
So using:
import _root_.com.me.project.database.Database
Does work as mentioned below. But should it work without the _root_? The comments so far seem to indicate that it should.
Answer:
So it turns out that I just needed to clean the project for the import to work properly, using both:
import _root_.com.me.project.database.Database
import com.me.project.database.Database
are valid solutions. Eclipse had just gotten confused.
imports can be relative. Is that the only import you have? be careful with other imports like
import com.me
ultimately, this should fix it, then you can try to find more about it:
import _root_.com.me.project.database.Database
In my case I also needed to check that object which is not found as a member of package is compiled successfully.
I realize this question already has an accepted answer, but since I experienced the same problem but with a different cause I figured I'd add an answer.
I had a bunch of interdependent projects which suddenly needed a root import in order to compile. It turned out that I had duplicated the package declaration in a single file. This caused some kind of chain reaction and made it very hard to find the source of the problem.
In summary I had
package foo.bar
package foo.bar
on the top of the file instead of just
package foo.bar
Hope this saves someone some really tedious error hunting.
In my case I had to run sbt clean.
I had faced similar issue where IntelliJ showed error on importing one file from the same project.
What did not resolve the issue in my case:
adding _root_ in import statement
sbt clean
restarting machine
What actually resolved the issue:
main menu => select File => click on Invalidate Caches / Restart => pop-up dailog => click on invalidate the caches and restart.
I was using IDEA (2019.2.2 Ultimate Edition) on macOs mojave 10.14.6
Java -> Scala conversion without cleaning
Don't forget to clean if you convert some file in a project from Java to Scala. I had a continuous integration build running where I couldn't get things to work, even though the build was working locally, after I had converted a Java class into a Scala object. Solution: add 'clean' to the build procedure on the CI server. The name of the generated .class file in Scala is slightly different than for a Java class, I believe, so this is very likely what was causing the issue.
If you are using gradle as your build tool, then ensure that jar task is not disabled.
I had multiple modules in my project, where one module was dependent on a few other modules. However, I had disabled jar task in build.gradle:
jar {
enabled = false
}
That caused it to fail to resolve classes in the dependent modules and fail with the above error.
I will share my story, just in case it may help someone.
Scenario: intellij compilation succeeds, but gradle build fails on import com.foo.Bar, where Bar is a scala class.
TLDR reason: Bar was located under src/main/java/... as opposed to src/main/scala/...
Actual reason: Bar was not being compiled by compileScala gradle task (from gradle scala plugin) because it looks for scala sources only under src/<sourceSet>/scala.
From docs.gradle.org:
All the Scala source directories can contain Scala and Java code. The
Java source directories may only contain Java source code.
Hope this helps
I had a similar problem but none of the solutions here worked for me. What did work however was a simple restart of my machine.
Perhaps it was something with my Intellij but after a quick restart, everything seems to be working fine.
I had a similar situation, which was failing in both IntelliJ and maven on the command line. I went to apply the suggested temp fix (adding _root_) but intellij was glitching so bad that wasn't even possible.
Eventually I noticed that I had mis-created a package so that it repeated the whole path of the package. That meant that the directory my class was in had a subfolder called "com", and the start of my file looked like:
package com.mycompany.mydept.myproject.myfunctionality.sub1
import com.holdenkarau.spark.testing.DataFrameSuiteBase
where I had another package called
com.mycompany.mydept.myproject.myfunctionality.sub1.com.mycompany.mydept.myproject.myfunctionality.sub2
And the compiler was looking for "holdenkarau" under com.mycompany.mydept.myproject.myfunctionality.com and failing.
I had this issue while using Intellij and the built-in sbt shell (precisely, I was trying to run the command console, which invokes a compiler check of the code).
In my case, after trying the other suggested solutions on this thread, I found that I could restart the sbt shell and it would go away. There's a button on the left-hand side of a looped green arrow and a small grey square which does this in one click (obviously, this is subject to Jet Brains not changing the design of the IDE!!!).
I hope this helps some people get past this issue quickly.
In my case, In Intellij, Just renaming the package file to something else >> see if it updates the import statements >> run the code >> then renaming back to the original name worked.