Skip to content

Commit 8d4b5ea

Browse files
FatmeFatme Havaluova
authored and
Fatme Havaluova
committed
* Implement lockfile to allow parallel execution
* Symlink option
1 parent e9520b3 commit 8d4b5ea

17 files changed

+171
-64
lines changed

lib/bootstrap.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ $injector.requireCommand("emulate", "./commands/emulate");
3333
$injector.requireCommand("list-devices", "./commands/list-devices");
3434

3535
$injector.require("npm", "./node-package-manager");
36+
$injector.require("lockfile", "./lockfile");
3637
$injector.require("config", "./config");
3738
$injector.require("optionsService", "./services/options-service");

lib/declarations.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
interface INodePackageManager {
2-
getCacheRootPath(): IFuture<string>;
2+
getCacheRootPath(): string;
33
addToCache(packageName: string, version: string): IFuture<void>;
44
cacheUnpack(packageName: string, version: string, unpackTarget?: string): IFuture<void>;
55
load(config?: any): IFuture<void>;
@@ -21,3 +21,7 @@ interface IApplicationPackage {
2121
time: Date;
2222
}
2323

24+
interface ILockFile {
25+
lock(): IFuture<void>;
26+
unlock(): IFuture<void>;
27+
}

lib/definitions/lockfile.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
declare module "lockfile" {
2+
export function lock(lockFilename: string, lockParams: ILockParams, callback: (err: Error) => void): void;
3+
export function lockSync(lockFilename: string, lockParams: any): void;
4+
export function unlock(lockFilename: string, callback: (err: Error) => void): void;
5+
export function unlockSync(lockFilename: string): void;
6+
7+
interface ILockSyncParams {
8+
retries: number;
9+
stale: number;
10+
}
11+
12+
interface ILockParams extends ILockSyncParams {
13+
retryWait: number;
14+
}
15+
}

lib/lockfile.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
///<reference path=".d.ts"/>
2+
"use strict";
3+
4+
import Future = require("fibers/future");
5+
import lockfile = require("lockfile");
6+
import path = require("path");
7+
import options = require("./options");
8+
9+
export class LockFile implements ILockFile {
10+
private static LOCK_FILENAME = path.join(options["profile-dir"], ".lock");
11+
private static LOCK_EXPIRY_PERIOD_SEC = 180;
12+
private static LOCK_PARAMS = {
13+
retryWait: 100,
14+
retries: LockFile.LOCK_EXPIRY_PERIOD_SEC*10,
15+
stale: LockFile.LOCK_EXPIRY_PERIOD_SEC*1000
16+
};
17+
18+
public lock(): IFuture<void> {
19+
var future = new Future<void>();
20+
lockfile.lock(LockFile.LOCK_FILENAME, LockFile.LOCK_PARAMS, (err: Error) => {
21+
if(err) {
22+
future.throw(err);
23+
} else {
24+
future.return();
25+
}
26+
});
27+
return future;
28+
}
29+
30+
public unlock(): IFuture<void> {
31+
var future = new Future<void>();
32+
lockfile.unlock(LockFile.LOCK_FILENAME, (err: Error) => {
33+
if(err) {
34+
future.throw(err);
35+
} else {
36+
future.return();
37+
}
38+
});
39+
return future;
40+
}
41+
}
42+
$injector.register("lockfile", LockFile);

lib/nativescript-cli.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
///<reference path=".d.ts"/>
22
"use strict";
3-
43
import path = require("path");
54

65
require("./bootstrap");

lib/node-package-manager.ts

Lines changed: 54 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,22 @@ export class NodePackageManager implements INodePackageManager {
1414
private static NPM_REGISTRY_URL = "http://registry.npmjs.org/";
1515

1616
private versionsCache: IDictionary<string[]>;
17-
private isLoaded: boolean;
1817

1918
constructor(private $logger: ILogger,
2019
private $errors: IErrors,
2120
private $httpClient: Server.IHttpClient,
22-
private $staticConfig: IStaticConfig,
23-
private $fs: IFileSystem) {
21+
private $fs: IFileSystem,
22+
private $lockfile: ILockFile) {
2423
this.versionsCache = {};
24+
this.load().wait();
2525
}
2626

27-
public getCacheRootPath(): IFuture<string> {
28-
return (() => {
29-
this.load().wait();
30-
return npm.cache;
31-
}).future<string>()();
27+
public getCacheRootPath(): string {
28+
return npm.cache;
3229
}
3330

3431
public addToCache(packageName: string, version: string): IFuture<void> {
35-
return (() => {
36-
this.load().wait();
37-
this.addToCacheCore(packageName, version).wait();
38-
}).future<void>()();
32+
return this.addToCacheCore(packageName, version);
3933
}
4034

4135
public load(config?: any): IFuture<void> {
@@ -52,29 +46,22 @@ export class NodePackageManager implements INodePackageManager {
5246

5347
public install(packageName: string, opts?: INpmInstallOptions): IFuture<string> {
5448
return (() => {
55-
try {
56-
this.load().wait(); // It's obligatory to execute load before whatever npm function
5749

50+
this.$lockfile.lock().wait();
51+
52+
try {
5853
var packageToInstall = packageName;
5954
var pathToSave = (opts && opts.pathToSave) || npm.cache;
6055
var version = (opts && opts.version) || null;
61-
var isSemanticVersioningDisabled = options.frameworkPath ? true : false; // We need to disable sem versioning for local packages
6256

63-
if(version) {
64-
this.validateVersion(packageName, version).wait();
65-
packageToInstall = packageName + "@" + version;
66-
}
67-
68-
this.installCore(packageToInstall, pathToSave, isSemanticVersioningDisabled).wait();
57+
return this.installCore(packageToInstall, pathToSave, version).wait();
6958
} catch(error) {
7059
this.$logger.debug(error);
71-
this.$errors.fail(NodePackageManager.NPM_LOAD_FAILED);
60+
this.$errors.fail("%s. Error: %s", NodePackageManager.NPM_LOAD_FAILED, error);
61+
} finally {
62+
this.$lockfile.unlock().wait();
7263
}
7364

74-
var pathToNodeModules = path.join(pathToSave, "node_modules");
75-
var folders = this.$fs.readDirectory(pathToNodeModules).wait();
76-
return path.join(pathToNodeModules, folders[0]);
77-
7865
}).future<string>()();
7966
}
8067

@@ -86,21 +73,40 @@ export class NodePackageManager implements INodePackageManager {
8673
}).future<string>()();
8774
}
8875

89-
private installCore(packageName: string, pathToSave: string, isSemanticVersioningDisabled: boolean): IFuture<void> {
90-
var currentVersion = this.$staticConfig.version;
91-
if(!semver.valid(currentVersion)) {
92-
this.$errors.fail("Invalid version.");
93-
}
94-
95-
if(!isSemanticVersioningDisabled) {
96-
var incrementedVersion = semver.inc(currentVersion, constants.ReleaseType.MINOR);
97-
if(packageName.indexOf("@") < 0) {
98-
packageName = packageName + "@<" + incrementedVersion;
76+
private installCore(packageName: string, pathToSave: string, version: string): IFuture<string> {
77+
return (() => {
78+
if (options.frameworkPath) {
79+
if (this.$fs.getFsStats(options.frameworkPath).wait().isFile()) {
80+
this.npmInstall(packageName, pathToSave, version).wait();
81+
var pathToNodeModules = path.join(pathToSave, "node_modules");
82+
var folders = this.$fs.readDirectory(pathToNodeModules).wait();
83+
return path.join(pathToNodeModules, folders[0]);
84+
}
85+
return options.frameworkPath;
86+
} else {
87+
var version = version || this.getLatestVersion(packageName).wait();
88+
var packagePath = path.join(npm.cache, packageName, version, "package");
89+
if (this.isPackageCached(packagePath).wait()) {
90+
if(!this.isPackageUnpacked(packagePath).wait()) {
91+
this.cacheUnpack(packageName, version).wait();
92+
}
93+
return packagePath;
94+
} else {
95+
this.npmInstall(packageName, pathToSave, version).wait();
96+
return path.join(pathToSave, "node_modules", packageName);
97+
}
9998
}
100-
}
99+
}).future<string>()();
100+
}
101101

102+
private npmInstall(packageName: string, pathToSave: string, version: string): IFuture<void> {
102103
this.$logger.out("Installing ", packageName);
103104

105+
var incrementedVersion = semver.inc(version, constants.ReleaseType.MINOR);
106+
if (!options.frameworkPath && packageName.indexOf("@") < 0) {
107+
packageName = packageName + "@<" + incrementedVersion;
108+
}
109+
104110
var future = new Future<void>();
105111
npm.commands["install"](pathToSave, packageName, (err: Error, data: any) => {
106112
if(err) {
@@ -113,6 +119,17 @@ export class NodePackageManager implements INodePackageManager {
113119
return future;
114120
}
115121

122+
private isPackageCached(packagePath: string): IFuture<boolean> {
123+
return this.$fs.exists(packagePath);
124+
}
125+
126+
private isPackageUnpacked(packagePath: string): IFuture<boolean> {
127+
return (() => {
128+
return this.$fs.getFsStats(packagePath).wait().isDirectory() &&
129+
helpers.enumerateFilesInDirectorySync(packagePath).length > 1;
130+
}).future<boolean>()();
131+
}
132+
116133
private addToCacheCore(packageName: string, version: string): IFuture<void> {
117134
var future = new Future<void>();
118135
npm.commands["cache"].add(packageName, version, undefined, (err: Error, data: any) => {

lib/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var knownOpts:any = {
1313
"release": Boolean,
1414
"device": Boolean,
1515
"emulator": Boolean,
16+
"symlink": Boolean,
1617
"keyStorePath": String,
1718
"keyStorePassword": String,
1819
"keyStoreAlias": String,

lib/services/android-project-service.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,8 @@ class AndroidProjectService implements IPlatformProjectService {
4949
return (() => {
5050
this.validateAndroidTarget(frameworkDir); // We need framework to be installed to validate android target so we can't call this method in validate()
5151

52-
var paths = "assets libs res".split(' ').map(p => path.join(frameworkDir, p));
53-
shell.cp("-R", paths, projectRoot);
54-
55-
paths = ".project AndroidManifest.xml project.properties".split(' ').map(p => path.join(frameworkDir, p));
56-
shell.cp("-f", paths, projectRoot);
52+
this.copyFiles(projectRoot, frameworkDir, "assets libs res").wait();
53+
this.copyFiles(projectRoot, frameworkDir, ".project AndroidManifest.xml project.properties").wait();
5754

5855
// Create src folder
5956
var packageName = this.$projectData.projectId;
@@ -124,6 +121,17 @@ class AndroidProjectService implements IPlatformProjectService {
124121
return [".jar", ".dat"];
125122
}
126123

124+
private copyFiles(projectRoot: string, frameworkDir: string, files: string): IFuture<void> {
125+
return (() => {
126+
var paths = files.split(' ').map(p => path.join(frameworkDir, p));
127+
if(options.symlink) {
128+
_.each(paths, p => this.$fs.symlink(p, projectRoot).wait());
129+
} else {
130+
shell.cp("-R", paths, projectRoot);
131+
}
132+
}).future<void>()();
133+
}
134+
127135
private spawn(command: string, args: string[]): IFuture<void> {
128136
if (hostInfo.isWindows()) {
129137
args.unshift('/s', '/c', command);

lib/services/ios-project-service.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ class IOSProjectService implements IPlatformProjectService {
6262

6363
public createProject(projectRoot: string, frameworkDir: string): IFuture<void> {
6464
return (() => {
65-
shell.cp("-R", path.join(frameworkDir, "*"), projectRoot);
65+
if(options.symlink) {
66+
this.$fs.symlink(frameworkDir, projectRoot);
67+
} else {
68+
shell.cp("-R", path.join(frameworkDir, "*"), projectRoot);
69+
}
6670
}).future<void>()();
6771
}
6872

lib/services/platform-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ export class PlatformService implements IPlatformService {
445445

446446
private getNpmCacheDirectoryCore(packageName: string, version: string): IFuture<string> {
447447
return (() => {
448-
var npmCacheRoot = this.$npm.getCacheRootPath().wait();
448+
var npmCacheRoot = this.$npm.getCacheRootPath();
449449
return path.join(npmCacheRoot, packageName, version, "package");
450450
}).future<string>()();
451451
}

lib/services/project-service.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ export class ProjectService implements IProjectService {
4343
var appDirectory = path.join(projectDir, constants.APP_FOLDER_NAME);
4444
var appPath: string = null;
4545

46-
if(customAppPath) {
46+
if (customAppPath) {
4747
this.$logger.trace("Using custom app from %s", customAppPath);
4848

4949
// Make sure that the source app/ is not a direct ancestor of a target app/
5050
var relativePathFromSourceToTarget = path.relative(customAppPath, appDirectory);
5151
// path.relative returns second argument if the paths are located on different disks
5252
// so in this case we don't need to make the check for direct ancestor
53-
if(relativePathFromSourceToTarget !== appDirectory) {
53+
if (relativePathFromSourceToTarget !== appDirectory) {
5454
var doesRelativePathGoUpAtLeastOneDir = relativePathFromSourceToTarget.split(path.sep)[0] === "..";
5555
if (!doesRelativePathGoUpAtLeastOneDir) {
5656
this.$errors.fail("Project dir %s must not be created at/inside the template used to create the project %s.", projectDir, customAppPath);
@@ -67,28 +67,29 @@ export class ProjectService implements IProjectService {
6767
}
6868

6969
try {
70-
this.createProjectCore(projectDir, appPath, projectId, false).wait();
71-
} catch(err) {
70+
this.createProjectCore(projectDir, appPath, projectId).wait();
71+
} catch (err) {
7272
this.$fs.deleteDirectory(projectDir).wait();
7373
throw err;
7474
}
7575

76+
7677
this.$logger.out("Project %s was successfully created", projectName);
7778

7879
}).future<void>()();
7980
}
8081

81-
private createProjectCore(projectDir: string, appPath: string, projectId: string, symlink?: boolean): IFuture<void> {
82+
private createProjectCore(projectDir: string, appSourcePath: string, projectId: string): IFuture<void> {
8283
return (() => {
83-
if(!this.$fs.exists(projectDir).wait()) {
84-
this.$fs.createDirectory(projectDir).wait();
85-
}
86-
if(symlink) {
87-
// TODO: Implement support for symlink the app folder instead of copying
84+
this.$fs.ensureDirectoryExists(projectDir).wait();
85+
86+
var appDestinationPath = path.join(projectDir, constants.APP_FOLDER_NAME);
87+
this.$fs.createDirectory(appDestinationPath).wait();
88+
89+
if(options.symlink) {
90+
this.$fs.symlink(appSourcePath, appDestinationPath).wait();
8891
} else {
89-
var appDir = path.join(projectDir, constants.APP_FOLDER_NAME);
90-
this.$fs.createDirectory(appDir).wait();
91-
shell.cp('-R', path.join(appPath, "*"), appDir);
92+
shell.cp('-R', path.join(appSourcePath, "*"), appDestinationPath);
9293
}
9394
this.createBasicProjectStructure(projectDir, projectId).wait();
9495
}).future<void>()();

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"fibers": "https://github.com/icenium/node-fibers/tarball/master",
3131
"filesize": "2.0.3",
3232
"iconv-lite": "0.4.4",
33+
"lockfile": "1.0.0",
3334
"log4js": "0.6.9",
3435
"mkdirp": "0.3.5",
3536
"mute-stream": "0.0.4",

test/platform-commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ function createTestInjector() {
7777
testInjector.registerCommand("platform|add", PlatformAddCommandLib.AddPlatformCommand);
7878
testInjector.registerCommand("platform|remove", PlatformRemoveCommandLib.RemovePlatformCommand);
7979
testInjector.registerCommand("platform|update", PlatformUpdateCommandLib.UpdatePlatformCommand);
80+
testInjector.register("lockfile", { });
8081

8182

8283
return testInjector;

test/platform-service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ function createTestInjector() {
3737
testInjector.register('androidEmulatorServices', {});
3838
testInjector.register('projectDataService', stubs.ProjectDataService);
3939
testInjector.register('prompter', {});
40+
testInjector.register('lockfile', stubs.LockFile);
4041

4142
return testInjector;
4243
}
@@ -151,7 +152,7 @@ describe('Platform Service Tests', () => {
151152
it("should fail when the versions are the same", () => {
152153
var npm: INodePackageManager = testInjector.resolve("npm");
153154
npm.getLatestVersion = () => (() => "0.2.0").future<string>()();
154-
npm.getCacheRootPath = () => (() => "").future<string>()();
155+
npm.getCacheRootPath = () => "";
155156

156157
(() => platformService.updatePlatforms(["android"]).wait()).should.throw();
157158
});

0 commit comments

Comments
 (0)