Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit c9b23dc

Browse files
committed
Updating Swift support with 4.2 runtime
1 parent cc11269 commit c9b23dc

File tree

3 files changed

+85
-127
lines changed

3 files changed

+85
-127
lines changed

README.md

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -393,40 +393,44 @@ func main(args: [String:Any]) -> [String:Any] {
393393

394394
If you want to return an error message, return an object with an `error` property with the message.
395395

396-
## Writing Functions - Pre-Compiled Swift Binaries
396+
### Codable Support
397+
398+
Swift 4 runtimes support [Codable types](https://developer.apple.com/documentation/swift/codable) to handle the converting between JSON input parameters and response types to native Swift types.
399+
400+
```swift
401+
struct Employee: Codable {
402+
let id: Int?
403+
let name: String?
404+
}
405+
// codable main function
406+
func main(input: Employee, respondWith: (Employee?, Error?) -> Void) -> Void {
407+
// For simplicity, just passing same Employee instance forward
408+
respondWith(input, nil)
409+
}
410+
```
411+
412+
### Pre-Compiled Swift Binaries
397413

398414
OpenWhisk supports creating Swift actions from a pre-compiled binary. This reduces startup time for Swift actions by removing the need for a dynamic compilation step.
399415

400-
In the `serverless.yaml` file, the `handler` property can refer to the compiled binary file produced by the build.
416+
In the `serverless.yaml` file, the `handler` property can refer to the zip file containing a binary file produced by the build.
401417

402418
```yaml
403419
functions:
404420
hello:
405-
handler: .build/release/Hello
421+
handler: action.zip
406422
```
407423

408-
This configuration will generate the deployment package for that function containing only this binary file. It will not include other local files, e.g. Swift source files.
409-
410-
Pre-compiled Swift actions must be compatible with the platform runtime and architecture. There is an [open-source Swift package](https://packagecatalog.com/package/jthomas/OpenWhiskAction) (`OpenWhiskAction`) that handles wrapping functions within a shim to support runtime execution.
424+
Compiling a single Swift file to a binary can be handled using this Docker command with the OpenWhisk Swift runtime image. `main.swift` is the file containing the swift code and `action.zip` is the zip archive produced.
411425

412426
```
413-
import OpenWhiskAction
414-
415-
func hello(args: [String:Any]) -> [String:Any] {
416-
if let name = args["name"] as? String {
417-
return [ "greeting" : "Hello \(name)!" ]
418-
} else {
419-
return [ "greeting" : "Hello stranger!" ]
420-
}
421-
}
422-
423-
OpenWhiskAction(main: hello)
427+
docker run -i openwhisk/action-swift-v4.2 -compile main < main.swift > action.zip
424428
```
425429
426-
Binaries produced by the Swift build process must be generated for the correct platform architecture. This Docker command will compile Swift sources files using the relevant Swift environment.
430+
Swift packages containing multiple source files with a package descriptor (`Package.swift` ) can be built using the following command.
427431
428432
```
429-
docker run --rm -it -v $(pwd):/swift-package openwhisk/action-swift-v3.1.1 bash -e -c "cd /swift-package && swift build -v -c release"
433+
zip - -r * | docker run -i openwhisk/action-swift-v4.2 -compile main > action.zip
430434
```
431435
432436
## Writing Functions - Java

compile/functions/runtimes/swift.js

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
'use strict';
22

3+
const fs = require('fs-extra')
34
const BaseRuntime = require('./base')
45
const JSZip = require("jszip")
56

@@ -11,29 +12,49 @@ class Swift extends BaseRuntime {
1112
}
1213

1314
convertHandlerToPath (functionHandler) {
14-
if (this.isBuildPath(functionHandler)) {
15+
if (this.isZipFile(functionHandler)) {
1516
return functionHandler
1617
}
1718

1819
return super.convertHandlerToPath(functionHandler)
1920
}
2021

21-
isBuildPath (path) {
22-
return path.startsWith('.build/')
22+
calculateFunctionMain(functionObject) {
23+
if (this.isZipFile(functionObject.handler)) {
24+
return 'main'
25+
}
26+
27+
return super.calculateFunctionMain(functionObject)
28+
}
29+
30+
isZipFile (path) {
31+
return path.endsWith('.zip')
32+
}
33+
34+
readHandlerFile (path) {
35+
const contents = fs.readFileSync(path)
36+
const encoding = this.isZipFile(path) ? 'base64' : 'utf8'
37+
return contents.toString(encoding)
2338
}
2439

25-
// Ensure zip package used to deploy action has the correct artifacts for the runtime.
26-
// Swift source actions must have the function code in `main.swift`.
27-
// Swift binary actions must have the binary as `./build/release/Action`.
28-
processActionPackage (handlerFile, zip) {
29-
return zip.file(handlerFile).async('nodebuffer').then(data => {
30-
if (this.isBuildPath(handlerFile)) {
31-
zip = new JSZip()
32-
return zip.file('.build/release/Action', data)
33-
}
34-
zip.remove(handlerFile)
35-
return zip.file('main.swift', data)
36-
})
40+
exec (functionObject) {
41+
const main = this.calculateFunctionMain(functionObject);
42+
const kind = this.calculateKind(functionObject);
43+
const handlerPath = this.convertHandlerToPath(functionObject.handler)
44+
45+
if (!this.isValidFile(handlerPath)) {
46+
throw new this.serverless.classes.Error(`Function handler (${handlerPath}) does not exist.`)
47+
}
48+
49+
const code = this.readHandlerFile(handlerPath)
50+
const binary = this.isZipFile(handlerPath)
51+
const exec = { main, kind, code, binary }
52+
53+
if (functionObject.hasOwnProperty('image')) {
54+
exec.image = functionObject.image
55+
}
56+
57+
return Promise.resolve(exec)
3758
}
3859
}
3960

compile/functions/runtimes/tests/swift.js

Lines changed: 26 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -56,30 +56,39 @@ describe('Swift', () => {
5656
});
5757

5858
describe('#exec()', () => {
59-
it('should return swift exec definition', () => {
59+
it('should return swift exec with source file handler', () => {
6060
const fileContents = 'some file contents';
6161
const handler = 'handler.some_func';
62-
63-
const exec = { main: 'some_func', kind: 'swift:default', code: new Buffer(fileContents) };
64-
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
65-
expect(functionObj.handler).to.equal(handler);
66-
return Promise.resolve(new Buffer(fileContents));
62+
node.isValidFile = () => true
63+
sandbox.stub(fs, 'readFileSync', (path) => {
64+
expect(path).to.equal('handler.swift');
65+
return Buffer.from(fileContents)
6766
});
67+
68+
const exec = { main: 'some_func', binary: false, kind: 'swift:default', code: fileContents };
6869
return expect(node.exec({ handler, runtime: 'swift'}))
6970
.to.eventually.deep.equal(exec);
7071
})
7172

72-
it('should return swift exec definition with custom image', () => {
73-
const fileContents = 'some file contents';
74-
const handler = 'handler.some_func';
73+
it('should return swift exec with zip file handler', () => {
74+
const handler = 'my_file.zip';
75+
node.isValidFile = () => true
7576

76-
const exec = { main: 'some_func', kind: 'blackbox', image: 'foo', code: new Buffer(fileContents) };
77-
sandbox.stub(node, 'generateActionPackage', (functionObj) => {
78-
expect(functionObj.handler).to.equal(handler);
79-
return Promise.resolve(new Buffer(fileContents));
80-
});
81-
return expect(node.exec({ handler, runtime: 'swift', image: 'foo' }))
77+
const zip = new JSZip();
78+
const source = 'binary file contents'
79+
zip.file("exec", source);
80+
81+
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
82+
sandbox.stub(fs, 'readFileSync', (path) => {
83+
expect(path).to.equal(handler);
84+
return zipped
85+
});
86+
87+
const b64 = zipped.toString('base64')
88+
const exec = { main: 'main', binary: true, kind: 'swift:default', code: b64 };
89+
return expect(node.exec({ handler, runtime: 'swift'}))
8290
.to.eventually.deep.equal(exec);
91+
})
8392
})
8493
});
8594

@@ -88,84 +97,8 @@ describe('Swift', () => {
8897
expect(node.convertHandlerToPath('file.func')).to.be.equal('file.swift')
8998
})
9099

91-
it('should return file path for build binaries', () => {
92-
expect(node.convertHandlerToPath('.build/release/Action')).to.be.equal('.build/release/Action')
93-
})
94-
})
95-
96-
describe('#generateActionPackage()', () => {
97-
it('should throw error for missing handler file', () => {
98-
expect(() => node.generateActionPackage({handler: 'does_not_exist.main'}))
99-
.to.throw(Error, 'Function handler (does_not_exist.swift) does not exist.');
100-
})
101-
102-
it('should read service artifact and add package.json for handler', () => {
103-
node.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
104-
node.isValidFile = () => true
105-
const source = 'func main(args: [String:Any]) -> [String:Any] {\nreturn ["hello": "world"]\n}'
106-
const zip = new JSZip();
107-
zip.file("handler.swift", source);
108-
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
109-
sandbox.stub(fs, 'readFile', (path, cb) => {
110-
expect(path).to.equal('/path/to/zip_file.zip');
111-
cb(null, zipped);
112-
});
113-
return node.generateActionPackage({handler: 'handler.main'}).then(data => {
114-
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
115-
expect(zip.file("handler.swift")).to.be.equal(null)
116-
return zip.file("main.swift").async("string").then(code => {
117-
expect(code).to.be.equal(source)
118-
})
119-
})
120-
})
121-
});
122-
})
123-
124-
it('should handle service artifact for individual function handler', () => {
125-
const functionObj = {handler: 'handler.main', package: { artifact: '/path/to/zip_file.zip'}}
126-
node.serverless.service.package = {individually: true};
127-
node.isValidFile = () => true
128-
129-
const zip = new JSZip();
130-
const source = 'func main(args: [String:Any]) -> [String:Any] {\nreturn ["hello": "world"]\n}'
131-
zip.file("handler.swift", source);
132-
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
133-
sandbox.stub(fs, 'readFile', (path, cb) => {
134-
expect(path).to.equal('/path/to/zip_file.zip');
135-
cb(null, zipped);
136-
});
137-
return node.generateActionPackage(functionObj).then(data => {
138-
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
139-
expect(zip.file("handler.swift")).to.be.equal(null)
140-
return zip.file("main.swift").async("string").then(code => {
141-
expect(code).to.be.equal(source)
142-
})
143-
})
144-
})
145-
});
100+
it('should return file path for zip files', () => {
101+
expect(node.convertHandlerToPath('my_file.zip')).to.be.equal('my_file.zip')
146102
})
147-
148-
it('should create zip file with binary action', () => {
149-
node.serverless.service.package = {artifact: '/path/to/zip_file.zip'};
150-
node.isValidFile = () => true
151-
const zip = new JSZip();
152-
const source = 'binary file contents'
153-
zip.file(".build/release/foo/bar", source);
154-
return zip.generateAsync({type:"nodebuffer"}).then(zipped => {
155-
sandbox.stub(fs, 'readFile', (path, cb) => {
156-
expect(path).to.equal('/path/to/zip_file.zip');
157-
cb(null, zipped);
158-
});
159-
return node.generateActionPackage({handler: '.build/release/foo/bar'}).then(data => {
160-
return JSZip.loadAsync(new Buffer(data, 'base64')).then(zip => {
161-
return zip.file(".build/release/Action").async("string").then(contents => {
162-
expect(contents).to.be.equal(source)
163-
})
164-
})
165-
})
166-
});
167-
})
168-
169-
170103
})
171104
});

0 commit comments

Comments
 (0)