Skip to content

Commit 156de01

Browse files
committed
Merge pull request #392 from bryk/cross-compile
Cross compilation build pipeline
2 parents 37c1829 + 2794805 commit 156de01

File tree

6 files changed

+294
-102
lines changed

6 files changed

+294
-102
lines changed

build/backend.js

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
/**
1616
* @fileoverview Gulp tasks for compiling backend application.
1717
*/
18-
import del from 'del';
1918
import gulp from 'gulp';
19+
import lodash from 'lodash';
2020
import path from 'path';
2121

2222
import conf from './conf';
@@ -40,35 +40,28 @@ gulp.task('backend', ['package-backend-source'], function(doneFn) {
4040
});
4141

4242
/**
43-
* Compiles backend application in production mode and places the binary in the dist
44-
* directory.
43+
* Compiles backend application in production mode for the current architecture and places the
44+
* binary in the dist directory.
4545
*
4646
* The production binary difference from development binary is only that it contains all
47-
* dependencies inside it and is targeted for Linux.
47+
* dependencies inside it and is targeted for a specific architecture.
4848
*/
49-
gulp.task('backend:prod', ['package-backend-source', 'clean-dist'], function(doneFn) {
49+
gulp.task('backend:prod', ['package-backend-source', 'clean-dist'], function() {
5050
let outputBinaryPath = path.join(conf.paths.dist, conf.backend.binaryName);
51+
return backendProd([[outputBinaryPath, conf.arch.default]]);
52+
});
5153

52-
// Delete output binary first. This is required because prod build does not override it.
53-
del(outputBinaryPath)
54-
.then(
55-
function() {
56-
goCommand(
57-
[
58-
'build',
59-
'-a',
60-
'-installsuffix',
61-
'cgo',
62-
'-o',
63-
outputBinaryPath,
64-
conf.backend.packageName,
65-
],
66-
doneFn, {
67-
// Disable cgo package. Required to run on scratch docker image.
68-
CGO_ENABLED: '0',
69-
});
70-
},
71-
function(error) { doneFn(error); });
54+
/**
55+
* Compiles backend application in production mode for all architectures and places the
56+
* binary in the dist directory.
57+
*
58+
* The production binary difference from development binary is only that it contains all
59+
* dependencies inside it and is targeted specific architecture.
60+
*/
61+
gulp.task('backend:prod:cross', ['package-backend-source', 'clean-dist'], function() {
62+
let outputBinaryPaths =
63+
conf.paths.distCross.map((dir) => path.join(dir, conf.backend.binaryName));
64+
return backendProd(lodash.zip(outputBinaryPaths, conf.arch.list));
7265
});
7366

7467
/**
@@ -83,3 +76,42 @@ gulp.task('package-backend-source', function() {
8376
.src([path.join(conf.paths.backendSrc, '**/*'), path.join(conf.paths.backendTest, '**/*')])
8477
.pipe(gulp.dest(conf.paths.backendTmpSrc));
8578
});
79+
80+
/**
81+
* @param {!Array<!Array<string>>} outputBinaryPathsAndArchs array of
82+
* (output binary path, architecture) pairs
83+
* @return {!Promise}
84+
*/
85+
function backendProd(outputBinaryPathsAndArchs) {
86+
let promiseFn = (path, arch) => {
87+
return (resolve, reject) => {
88+
goCommand(
89+
[
90+
'build',
91+
'-a',
92+
'-installsuffix',
93+
'cgo',
94+
'-o',
95+
path,
96+
conf.backend.packageName,
97+
],
98+
(err) => {
99+
if (err) {
100+
reject(err);
101+
} else {
102+
resolve();
103+
}
104+
},
105+
{
106+
// Disable cgo package. Required to run on scratch docker image.
107+
CGO_ENABLED: '0',
108+
GOARCH: arch,
109+
});
110+
};
111+
};
112+
113+
let goCommandPromises = outputBinaryPathsAndArchs.map(
114+
(pathAndArch) => new Promise(promiseFn(pathAndArch[0], pathAndArch[1])));
115+
116+
return Promise.all(goCommandPromises);
117+
}

build/build.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,55 @@ import uglifySaveLicense from 'uglify-save-license';
2828
import path from 'path';
2929

3030
import conf from './conf';
31+
import {multiDest} from './multidest';
3132

3233
/**
33-
* Builds production package and places it in the dist directory.
34+
* Builds production package for current architecture and places it in the dist directory.
3435
*/
3536
gulp.task('build', ['backend:prod', 'build-frontend']);
3637

38+
/**
39+
* Builds production packages for all supported architecures and places them in the dist directory.
40+
*/
41+
gulp.task('build:cross', ['backend:prod:cross', 'build-frontend:cross']);
42+
43+
/**
44+
* Builds production version of the frontend application for the current architecture.
45+
*/
46+
gulp.task('build-frontend', ['assets', 'index:prod', 'clean-dist'], function() {
47+
return buildFrontend(conf.paths.distPublic);
48+
});
49+
50+
/**
51+
* Builds production version of the frontend application for all architecures.
52+
*/
53+
gulp.task('build-frontend:cross', ['assets:cross', 'index:prod', 'clean-dist'], function() {
54+
return buildFrontend(conf.paths.distPublicCross);
55+
});
56+
57+
/**
58+
* Copies assets to the dist directory for current architecture.
59+
*/
60+
gulp.task('assets', ['clean-dist'], function() { return assets([conf.paths.distPublic]); });
61+
62+
/**
63+
* Copies assets to the dist directory for all architectures.
64+
*/
65+
gulp.task(
66+
'assets:cross', ['clean-dist'], function() { return assets(conf.paths.distPublicCross); });
67+
68+
/**
69+
* Cleans all build artifacts.
70+
*/
71+
gulp.task('clean', ['clean-dist'], function() {
72+
return del([conf.paths.goWorkspace, conf.paths.tmp, conf.paths.coverage]);
73+
});
74+
75+
/**
76+
* Cleans all build artifacts in the dist/ folder.
77+
*/
78+
gulp.task('clean-dist', function() { return del([conf.paths.distRoot]); });
79+
3780
/**
3881
* Builds production version of the frontend application.
3982
*
@@ -42,8 +85,10 @@ gulp.task('build', ['backend:prod', 'build-frontend']);
4285
* 2. index.html is minified.
4386
* 3. CSS and JS assets are suffixed with version hash.
4487
* 4. Everything is saved in the dist directory.
88+
* @param {string|!Array<string>} outputDirs
89+
* @return {stream}
4590
*/
46-
gulp.task('build-frontend', ['assets', 'index:prod', 'clean-dist'], function() {
91+
function buildFrontend(outputDirs) {
4792
let htmlFilter = gulpFilter('*.html', {restore: true});
4893
let vendorCssFilter = gulpFilter('**/vendor.css', {restore: true});
4994
let vendorJsFilter = gulpFilter('**/vendor.js', {restore: true});
@@ -75,25 +120,14 @@ gulp.task('build-frontend', ['assets', 'index:prod', 'clean-dist'], function() {
75120
conservativeCollapse: true,
76121
}))
77122
.pipe(htmlFilter.restore)
78-
.pipe(gulp.dest(conf.paths.distPublic));
79-
});
123+
.pipe(multiDest(outputDirs));
124+
}
80125

81126
/**
82-
* Copies assets to the dist directory.
127+
* @param {string|!Array<string>} outputDirs
128+
* @return {stream}
83129
*/
84-
gulp.task('assets', ['clean-dist'], function() {
130+
function assets(outputDirs) {
85131
return gulp.src(path.join(conf.paths.assets, '/**/*'), {base: conf.paths.app})
86-
.pipe(gulp.dest(conf.paths.distPublic));
87-
});
88-
89-
/**
90-
* Cleans all build artifacts.
91-
*/
92-
gulp.task('clean', ['clean-dist'], function() {
93-
return del([conf.paths.goWorkspace, conf.paths.tmp, conf.paths.coverage]);
94-
});
95-
96-
/**
97-
* Cleans all build artifacts in the dist/ folder.
98-
*/
99-
gulp.task('clean-dist', function() { return del([conf.paths.dist]); });
132+
.pipe(multiDest(outputDirs));
133+
}

build/conf.js

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,40 @@ import path from 'path';
2222
*/
2323
const basePath = path.join(__dirname, '../');
2424

25+
/**
26+
* Compilation architecture configuration.
27+
*/
28+
const arch = {
29+
/**
30+
* Default architecture that the project is compiled to. Used for local development and testing.
31+
* TODO(bryk): Dynamically determine this based on current arch.
32+
*/
33+
default: 'amd64',
34+
/**
35+
* List of all supported architectures by this project.
36+
*/
37+
list: ['amd64', 'arm', 'arm64', 'ppc64le'],
38+
};
39+
40+
/**
41+
* Package version information.
42+
*/
43+
const version = {
44+
/**
45+
* Current release version of the project.
46+
*/
47+
release: 'v0.1.0',
48+
/**
49+
* Version name of the canary release of the project.
50+
*/
51+
canary: 'canary',
52+
};
53+
54+
/**
55+
* Base name for the docker image.
56+
*/
57+
const imageNameBase = 'gcr.io/google_containers/kubernetes-dashboard';
58+
2559
/**
2660
* Exported configuration object with common constants used in build pipeline.
2761
*/
@@ -53,23 +87,39 @@ export default {
5387
heapsterServerHost: 'http://localhost:8082',
5488
},
5589

90+
/**
91+
* Project compilation architecture info.
92+
*/
93+
arch: arch,
94+
5695
/**
5796
* Deployment constants configuration.
5897
*/
5998
deploy: {
6099
/**
61-
* The release version of the image.
100+
* Project version info.
62101
*/
63-
versionRelease: 'v0.1.0',
102+
version: version,
103+
64104
/**
65-
* The canary version name of the image. Canary is an image that is frequently published,
66-
* and has no release schedule.
105+
* Image name for the canary release for current architecture.
67106
*/
68-
versionCanary: 'canary',
107+
canaryImageName: `${imageNameBase}-${arch.default}:${version.canary}`,
108+
109+
/**
110+
* Image name for the versioned release for current architecture.
111+
*/
112+
releaseImageName: `${imageNameBase}-${arch.default}:${version.release}`,
113+
114+
/**
115+
* Image name for the canary release for all supported architecture.
116+
*/
117+
canaryImageNames: arch.list.map((arch) => `${imageNameBase}-${arch}:${version.canary}`),
118+
69119
/**
70-
* The name of the Docker image with the application.
120+
* Image name for the versioned release for all supported architecture.
71121
*/
72-
imageName: 'gcr.io/google_containers/kubernetes-dashboard',
122+
releaseImageNames: arch.list.map((arch) => `${imageNameBase}-${arch}:${version.release}`),
73123
},
74124

75125
/**
@@ -113,8 +163,11 @@ export default {
113163
coverage: path.join(basePath, 'coverage'),
114164
coverageReport: path.join(basePath, 'coverage/lcov'),
115165
deploySrc: path.join(basePath, 'src/deploy'),
116-
dist: path.join(basePath, 'dist'),
117-
distPublic: path.join(basePath, 'dist/public'),
166+
dist: path.join(basePath, 'dist', arch.default),
167+
distCross: arch.list.map((arch) => path.join(basePath, 'dist', arch)),
168+
distPublic: path.join(basePath, 'dist', arch.default, 'public'),
169+
distPublicCross: arch.list.map((arch) => path.join(basePath, 'dist', arch, 'public')),
170+
distRoot: path.join(basePath, 'dist'),
118171
externs: path.join(basePath, 'src/app/externs'),
119172
frontendSrc: path.join(basePath, 'src/app/frontend'),
120173
frontendTest: path.join(basePath, 'src/test/frontend'),

0 commit comments

Comments
 (0)