Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linux Support #1188

Merged
merged 147 commits into from
Sep 7, 2023
Merged

Linux Support #1188

merged 147 commits into from
Sep 7, 2023

Conversation

art-divin
Copy link
Collaborator

@art-divin art-divin commented Jul 30, 2023

Resolves #306

Description

This MR enables support on Linux (particularly was compiled under Ubuntu 22.04, but should work on other version as well if all dependencies are met).

Technical Details

A couple of things were disabled (#1198):

  1. Due to the fact that FileWatcher relies on macOS SDK and uses FSEvents framework, it was disabled, i.e. folder watching flag (--watch) is not working under Linux
  2. All JavaScriptCore functionality (ejs templates etc.) were disabled under Linux only
  3. NSObject available under Linux does not conform to KVC, thus tests in TypedSpec.generated.swift were moved to class definitions.

Notes

  1. getVaList was replaced with recommended withVaList() {} (used mostly for Codable-related code generation)
  2. NSException was replaced with fatalError only under Linux
  3. CFAbsoluteTimeGetCurrent was replaced with Date().timeIntervalSince1970 for all platforms
  4. FolderWatcher needs to be implemented probably using approach similar to this one
  5. sha256() of Data extension is implemented via swift-crypto package

Environment Setup

I have installed ubuntu VM through tart and updated to 22.04 according to this guide.

I had to run the following commands prior to being able to run bundle install in Sourcery:

  1. sudo apt install libffi-dev
  2. sudo apt install build-essential
  3. sudo apt install libsqlite3-dev
  4. sudo apt-get install libncurses5-dev

@SourceryBot
Copy link

SourceryBot commented Jul 30, 2023

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

@art-divin
Copy link
Collaborator Author

art-divin commented Aug 6, 2023

I was able to make tests green on macOS while preserving successful compilation under Linux 🔥

However, swift test does not run tests under Linux, i.e. it compiles for testing, and swift test list also generates plenty of discovered tests under .build/linux..../debug/*DiscoveredTests, but it does not execute any tests.

I also need support how to setup CircleCI to run linux and also compile Sourcery for releases under Linux @krzysztofzablocki do you have an insight? Maybe a simple hint, anything would be appreciated 👍🏻

🙏🏻

@art-divin art-divin changed the title Draft: Linux Support Linux Support Aug 6, 2023
@krzysztofzablocki
Copy link
Owner

krzysztofzablocki commented Aug 7, 2023

I don't really have experience with Linux but you could also move us to Github actions if you prefer, this project predates them but we could easily use GH actions now

@art-divin
Copy link
Collaborator Author

Thanks @krzysztofzablocki .

Apart from all Linux-related stuff, I have faced an issue when running tests with swift test. Seems like bundle is different when swift test is running tests (creating xctest file) and when Xcode does the same (generates xctest file) ->

When swift test is executed, the same project generates SourceryPackageTests.xctest bundle, which does not contain resources,
while when Xcode runs the same tests, bundle SourceryLibTests.xctest is generated, and that bundle contains described resources as per Package.swift spec.

Looking into it, but this is closely related to why .ejs are disabled for SPM-compiled sourcery (related to #244)

@art-divin
Copy link
Collaborator Author

art-divin commented Aug 7, 2023

Tried to rename SourceryLibTests to SourceryPackageTests just to make swift test "think" that it's OK to put resources there, and yet it did not help. Here's the difference:

Screenshot 2023-08-08 at 1 13 25 AM

My suspicion is that swift test does not support copying resources just yet, found something related to this here.

Update: found the relevant Swift forums thread here. I'll try the workaround, once successful, swift test would be used for running Sourcery tests.

@art-divin
Copy link
Collaborator Author

art-divin commented Aug 7, 2023

My current plan is:

  1. Make swift test work
  2. Enable GitHub Actions to compile for macOS and Linux
  3. Make tests executable under Linux
  4. Merge this MR

@art-divin art-divin mentioned this pull request Aug 12, 2023
art-divin added a commit that referenced this pull request Aug 12, 2023
Implemented GH actions for macOS and ubuntu; 
disabled both actions until #1188 is merged.
return try templatePaths(from: from).compactMap {
if $0.extension == "sourcerytemplate" {
let template = try JSONDecoder().decode(SourceryTemplate.self, from: $0.read())
switch template.instance.kind {
case .ejs:
guard EJSTemplate.ejsPath != nil else {
Log.warning("Skipping template \($0). JavaScript templates require EJS path to be set manually when using Sourcery built with Swift Package Manager. Use `--ejsPath` command line argument to set it.")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krzysztofzablocki here I was really unsure about this guard statements. Due to changes I have introduced in EJSTemplate.swift file in this MR, swift test started to work, but for tests, not for a release build which I am unsure how to verify.

Could you please share some insight on this, shall I leave these guard statements as they were, or would it actually work due to this change I have mentioned?

Thank you 🙏🏻

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we don't have the EJS contents then the templates wouldn't work but you could also reject it based on env since you mentioned JavascriptCore doesn't work on Linux

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice one, thanks. I'll revert these changes to throw a more precise error as before.

Newer SPM (I think 5.9) supports "resources embedded into binary executables" (see .embedInCode) - maybe we could include EJS template contents into code (encoded, say, with base16/64 encoding or as array of bytes) - I'd need to dig deeper into this, but sounds like some form of convenience for Sourcery. I'll revisit this later.

/// Defines enum case associated value
public final class AssociatedValue: NSObject, SourceryModel, AutoDescription, Typed, Annotated, Diffable, DynamicMemberLookup {
public subscript(dynamicMember member: String) -> Any? {
switch member {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list is not exhaustive, but just to make all unit tests work. It must be autogenerated like init(coder:) methods, and I'll address it in #1198

import Foundation

/// Defines Swift enum
public final class Enum: Type {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to limitations how #if statement can be used in Swift, I figured it is better to have separate files rather than having multiple definitions in already overcrowded files.

context("given array") {
it("doesnt modify the value") {
let result = generate("{% for key,value in type.MyClass.variables.2.annotations %}{{ value | toArray }}{% endfor %}")
expect(result).to(equal("[\"Hello\", \"beautiful\", \"World\"]"))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On linux, stringify from Stencil provides quotes around string values. I could not workaround it, and most likely this is due to differences in how NSObject works on two platforms.

it("can render variable isOptional") {
expect(generate("{{ type.Complex.variables.first.isOptional }}")).to(equal("0"))
}
#else
it("can render variable isOptional") {
expect(generate("{{ type.Complex.variables.first.isOptional }}")).to(equal("false"))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷🏻 Don't ask me why 🤷🏻 Really weird difference

@@ -1271,14 +1294,15 @@ class SourcerySpecTests: QuickSpec {
}.toNot(throwError())
}

#if canImport(ObjectiveC)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to crash in Swift compiler reported here.

@@ -82,6 +90,12 @@ class TemplatesTests: QuickSpec {
expect(generatedFileFilteredLines).to(equal(expectedFileFilteredLines))
}

#if !canImport(ObjectiveC)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Linux, static func setUp() is not called, so I needed to manually add this beforeSuite to generate files before tests are run.

#if canImport(ObjectiveC)
let contextSources = "\(resources)/Context"
#else
let contextSources = "\(resources)/Context_Linux"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different file folders are used for two reasons:

  1. SPM does not have a method to specify where to put the copied resources (Resource.copy(path:locatization:)).
  2. Because of the crash in Swift compiler, AutoCodable.swift file needs to be omitted when tests are run on Linux

@art-divin art-divin mentioned this pull request Sep 4, 2023
@Joannis
Copy link
Contributor

Joannis commented Sep 4, 2023

AMAZING. Looking forward to seeing this hopefully merged

Copy link
Owner

@krzysztofzablocki krzysztofzablocki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small comments but amazing work 👏

return try templatePaths(from: from).compactMap {
if $0.extension == "sourcerytemplate" {
let template = try JSONDecoder().decode(SourceryTemplate.self, from: $0.read())
switch template.instance.kind {
case .ejs:
guard EJSTemplate.ejsPath != nil else {
Log.warning("Skipping template \($0). JavaScript templates require EJS path to be set manually when using Sourcery built with Swift Package Manager. Use `--ejsPath` command line argument to set it.")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we don't have the EJS contents then the templates wouldn't work but you could also reject it based on env since you mentioned JavascriptCore doesn't work on Linux

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Linux Support
4 participants