-
-
Notifications
You must be signed in to change notification settings - Fork 625
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
Conversation
@kzaher if you have time, it would be better for you to review it since you are the author :) |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
}
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
Generated by 🚫 danger |
@kzaher @krzysztofzablocki any ideas how to make I was able to build template with SPM |
this improves compile time until we find a good way to link precompiled framework and only build one file
4147f7a
to
31ba29a
Compare
@krzysztofzablocki @kzaher can you please test my latest changes locally? I think it should work much faster now 🎉 |
f1e6876
to
8edb54f
Compare
# 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
@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 |
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 I also get some other compiler errors while trying to run the tests on 172ec96. |
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 |
@ilyapuchka now I see 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 :) |
That was actually an issue, with proper symlink it works 🎉 |
ac0bca7
to
880760e
Compare
Yes, if there is a folder in Sources with no |
I was trying to get it to print help page so I can figure out the arguments :) |
|
@krzysztofzablocki can we merge this? 🙏🏻 |
go ahead, just squash it 👍 |
No description provided.