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

Caching swift templates #240

Merged
merged 18 commits into from
May 9, 2017
Merged

Caching swift templates #240

merged 18 commits into from
May 9, 2017

Conversation

ilyapuchka
Copy link
Collaborator

No description provided.

@krzysztofzablocki
Copy link
Owner

@kzaher if you have time, it would be better for you to review it since you are the author :)

Copy link
Contributor

@kzaher kzaher left a comment

Choose a reason for hiding this comment

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

Hi @krzysztofzablocki , @ilyapuchka ,

I've made some suggestions. Hope it will help.

func render(types: Types, arguments: [String: NSObject]) throws -> String {
let context = TemplateContext(types: types, arguments: arguments)
let swiftCode = try SwiftTemplate.generateSwiftCode(templateContent: try sourcePath.read(), path: sourcePath)
static func loadOrBuild(template: SwiftTemplate, buildDir: Path, cachePath: Path?) throws -> Path {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be cool to make this something like.

extension SwiftTemplate {
     func build(buildDir: Path, cachePath: Path?) throws -> Path {
            return lazy("cached_binaries", source)  { () -> Data in
                    // build binary content and return it here
            }
     }
}

We could separate this method into generic caching functionality (lazy loading) and generating part.

I also think this entire method could be simplified.

If we change SwiftTemplate like I've suggested below, then SwiftTemplate becomes only a value type.
We can use source hash as a path to binary, and just check does it exist or not (inside lazy method).

Right now it seems to me we only use single path for all templates. It should probably be let cachedBinaryFile = cachePath.map({ $0 + Path("bin") + source.hash }).

We also wouldn't need this part of code

+        if let cachedTemplateFile = cachedTemplateFile,
 +            let cachedBinaryFile = cachedBinaryFile {
 +            do {
 +                let data = NSKeyedArchiver.archivedData(withRootObject: template)
 +                try cachedTemplateFile.write(data)
 +                try binaryFile.copy(cachedBinaryFile)
 +            } catch {
 +                try? cachePath?.delete()
 +            }
 +        }

then, because we don't store template. SwiftTemplate should be momentarily because it only depends on a couple of O(n) complexity string operations.

Copy link
Collaborator Author

@ilyapuchka ilyapuchka Apr 21, 2017

Choose a reason for hiding this comment

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

There is a separate path for each template, named by template name, so paths are fine now.
I like your idea about using hash as a file name, instead of storing it in archive. It will save us from unneeded unarchiving operations. I guess we should do that for parser results cache.

Regarding your last comment we need to store at least a binary, because it takes most of the time to compile generated code. It will be faster when runtime files will be precompiled but it will still take few seconds. With cached binary we just run it. If we combine it with your first suggestion to use hash as a name we can avoid storing main.swift and there will be no need in archiving and unarchiving its content at all

Copy link
Contributor

Choose a reason for hiding this comment

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

Let me try to restate my suggestion :)

class SwiftTemplate {
     let generatedCode: String
     let cachePath: String?
}

let t = SwiftTemplate(templatePath, cachePath) // parsing template and generating source code is done here in convenience init, it's fast, so no caching is needed

func render(....) {

    let binaryPath = Path(cacheDir + t.generatedCode.hash)
    if !binaryPath.exists {
         Path.move(t.compile(buildDir), binaryPath)  // store it in cache
    }
 
    /// serialize everything and run binaryPath  
}

Copy link
Collaborator Author

@ilyapuchka ilyapuchka Apr 21, 2017

Choose a reason for hiding this comment

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

exactly what I thought of 👍


class SwiftTemplate: NSObject, NSCoding, Template {

var sourcePath: Path
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need var? Actually, do we need sourcePath at all? We could only use it in convenience initializer that is made for parsing.


var sourcePath: Path
let code: String
let contentSha: String?
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure do we need this, this could be derived from code IMHO.

self.contentSha = code.sha256()
}

init(path: Path, cachePath: Path?) throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be transformed into convenience init that only takes path. It seems to me that cachePath is only related with loadOrBuild method, but I've given a suggestion to transform it into an extension on SwiftTemplate.

func build(buildDir: Path, cachePath: Path?) throws -> Path {

This would separate SwiftTemplate which could be considered as a value type with only let code: String.

I think it would make sense to extend func render(types: Types, arguments: [String: NSObject]) with cachePath so we could use build inside of it.

Alternative would be to also store let cachePath: String? and let source: String in SwiftTemplate, but I would still call build method inside render and make this just a dummy value type, although it's a NSObject.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For me it feels that it's better to keep things separate, parsing is parsing, rendering is rendering. But I'll keep looking in how to organise code better, that's not the final version.

@ilyapuchka ilyapuchka mentioned this pull request Apr 21, 2017
@SourceryBot
Copy link

SourceryBot commented Apr 22, 2017

1 Warning
⚠️ Big PR

Generated by 🚫 danger

@ilyapuchka
Copy link
Collaborator Author

@kzaher @krzysztofzablocki any ideas how to make swiftc link frameworks? I've tried -F and -framework but it feels like they have no effect. Maybe I miss some other flags to make it work, but I could not find any inspecting Xcode build commands.

I was able to build template with SPM swift build. But this requires not a framework binary but module build artefacts.

@ilyapuchka ilyapuchka force-pushed the feature/swift-templates-cache branch from 4147f7a to 31ba29a Compare April 24, 2017 22:39
@ilyapuchka
Copy link
Collaborator Author

@krzysztofzablocki @kzaher can you please test my latest changes locally? I think it should work much faster now 🎉

@ilyapuchka
Copy link
Collaborator Author

Kudos to @kylef for this helpful tip 🙇

@ilyapuchka ilyapuchka changed the title [WIP] caching swift templates Caching swift templates Apr 25, 2017
@ilyapuchka ilyapuchka force-pushed the feature/swift-templates-cache branch 3 times, most recently from f1e6876 to 8edb54f Compare April 25, 2017 22:35
# Conflicts:
#	CHANGELOG.md
#	Sourcery/Sourcery.swift
#	docs/Classes/Method.html
#	docs/docsets/Sourcery.docset/Contents/Resources/Documents/Classes/Method.html
#	docs/docsets/Sourcery.docset/Contents/Resources/Documents/search.json
#	docs/docsets/Sourcery.docset/Contents/Resources/docSet.dsidx
#	docs/docsets/Sourcery.tgz
#	docs/search.json
# Conflicts:
#	CHANGELOG.md
#	docs/Classes/TypeName.html
#	docs/docsets/Sourcery.docset/Contents/Resources/Documents/Classes/TypeName.html
#	docs/docsets/Sourcery.docset/Contents/Resources/Documents/search.json
#	docs/docsets/Sourcery.docset/Contents/Resources/docSet.dsidx
#	docs/docsets/Sourcery.tgz
#	docs/search.json
@ilyapuchka
Copy link
Collaborator Author

@krzysztofzablocki have some troubles here making SPM build work again. First it made me to move all the files to the single folder... Then I added a framework to targets in Package.swift, like this:

targets: [
        Target(name: "sourcery", dependencies: ["SourceryFramework"]),
        Target(name: "SourceryFramework"),
    ]

And this gives error, descriptive as any Apple error:

error: these referenced modules could not be found: SourceryFramework
fix: reference only valid modules

@kzaher
Copy link
Contributor

kzaher commented May 2, 2017

Hi @ilyapuchka ,

I believe for you to be able to write something like

targets: [
        Target(name: "sourcery", dependencies: ["SourceryFramework"]),
        Target(name: "SourceryFramework"),
    ]

You need to have SourceryFramework named directory under Sources that contains all of *.swift files in a flat structure, no subfolders, no other files.

Now I see this
screen shot 2017-05-02 at 22 58 02

I also get some other compiler errors while trying to run the tests on 172ec96.

@ilyapuchka
Copy link
Collaborator Author

That's exactly what I did, tried both with simlynk (like for sourcery module) and just copying all the sources directly. I also have fixed compiler errors after merge. I've push all of that, if you have a chance to look at it deeper would be cool

@kzaher
Copy link
Contributor

kzaher commented May 2, 2017

@ilyapuchka now I see

screen shot 2017-05-02 at 23 08 55

Doesn't look like this will compile 😀 Perhaps symlink gone wrong 😀 ¯\_(ツ)_/¯

I can probably hack a script that rebuilds this hierarchy if you like. We do this for RxSwift. Not the prettiest solution, but works :)

@ilyapuchka
Copy link
Collaborator Author

That was actually an issue, with proper symlink it works 🎉

@ilyapuchka ilyapuchka force-pushed the feature/swift-templates-cache branch from ac0bca7 to 880760e Compare May 2, 2017 21:19
@kzaher
Copy link
Contributor

kzaher commented May 2, 2017

Ha, interesting. I think those subfolders might be treated as submodules. Probably documented somewhere ¯\_(ツ)_/¯

Just one more thing :)

screen shot 2017-05-02 at 23 22 39

@ilyapuchka
Copy link
Collaborator Author

Yes, if there is a folder in Sources with no main.swift it's treated as a framework module.
Regarding the error that you get you probably need to update yaml (I guess you use it as you don't provide any cli params, right?) file as the paths now are relative to its location

@kzaher
Copy link
Contributor

kzaher commented May 2, 2017

I was trying to get it to print help page so I can figure out the arguments :)

@ilyapuchka
Copy link
Collaborator Author

./.build/debug/sourcery --help will do that.
There is in fact configuration file checked in already and by default we look at the current path and if there is config file we ignore cli arguments. Maybe we should change that and always use provided cli arguments. I've updated this config any way, so now it should work with just ./.build/debug/sourcery after you remove ejs template ofc.
Can you take another look at the changes when you have time? I'd like to work on some other features for swift templates like #275 or #243 but it would be better if they will be done on top of that to avoid more conflicts.

@ilyapuchka
Copy link
Collaborator Author

@krzysztofzablocki can we merge this? 🙏🏻

@krzysztofzablocki
Copy link
Owner

go ahead, just squash it 👍

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.

4 participants