Skip to content

Commit fa2f973

Browse files
FatmeFatme
Fatme
authored and
Fatme
committed
Merge pull request #140 from NativeScript/fatme/optimizations
Implement lockfile and symlink option
2 parents 676f12e + 156c047 commit fa2f973

18 files changed

+211
-80
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: ILockSyncParams): 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: 51 additions & 45 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,21 @@ 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
49+
this.$lockfile.lock().wait();
5750

51+
try {
5852
var packageToInstall = packageName;
5953
var pathToSave = (opts && opts.pathToSave) || npm.cache;
6054
var version = (opts && opts.version) || null;
61-
var isSemanticVersioningDisabled = options.frameworkPath ? true : false; // We need to disable sem versioning for local packages
62-
63-
if(version) {
64-
this.validateVersion(packageName, version).wait();
65-
packageToInstall = packageName + "@" + version;
66-
}
6755

68-
this.installCore(packageToInstall, pathToSave, isSemanticVersioningDisabled).wait();
56+
return this.installCore(packageToInstall, pathToSave, version).wait();
6957
} catch(error) {
7058
this.$logger.debug(error);
71-
this.$errors.fail(NodePackageManager.NPM_LOAD_FAILED);
59+
this.$errors.fail("%s. Error: %s", NodePackageManager.NPM_LOAD_FAILED, error);
60+
} finally {
61+
this.$lockfile.unlock().wait();
7262
}
7363

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

@@ -86,21 +72,39 @@ export class NodePackageManager implements INodePackageManager {
8672
}).future<string>()();
8773
}
8874

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-
}
75+
private installCore(packageName: string, pathToSave: string, version: string): IFuture<string> {
76+
return (() => {
77+
if (options.frameworkPath) {
78+
if (this.$fs.getFsStats(options.frameworkPath).wait().isFile()) {
79+
this.npmInstall(packageName, pathToSave, version).wait();
80+
var pathToNodeModules = path.join(pathToSave, "node_modules");
81+
var folders = this.$fs.readDirectory(pathToNodeModules).wait();
82+
return path.join(pathToNodeModules, folders[0]);
83+
}
84+
return options.frameworkPath;
85+
} else {
86+
var version = version || this.getLatestVersion(packageName).wait();
87+
var packagePath = path.join(npm.cache, packageName, version, "package");
88+
if (!this.isPackageCached(packagePath).wait()) {
89+
this.addToCacheCore(packageName, version).wait();
90+
}
9491

95-
if(!isSemanticVersioningDisabled) {
96-
var incrementedVersion = semver.inc(currentVersion, constants.ReleaseType.MINOR);
97-
if(packageName.indexOf("@") < 0) {
98-
packageName = packageName + "@<" + incrementedVersion;
92+
if(!this.isPackageUnpacked(packagePath).wait()) {
93+
this.cacheUnpack(packageName, version).wait();
94+
}
95+
return packagePath;
9996
}
100-
}
97+
}).future<string>()();
98+
}
10199

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

103+
var incrementedVersion = semver.inc(version, constants.ReleaseType.MINOR);
104+
if (!options.frameworkPath && packageName.indexOf("@") < 0) {
105+
packageName = packageName + "@<" + incrementedVersion;
106+
}
107+
104108
var future = new Future<void>();
105109
npm.commands["install"](pathToSave, packageName, (err: Error, data: any) => {
106110
if(err) {
@@ -113,6 +117,17 @@ export class NodePackageManager implements INodePackageManager {
113117
return future;
114118
}
115119

120+
private isPackageCached(packagePath: string): IFuture<boolean> {
121+
return this.$fs.exists(packagePath);
122+
}
123+
124+
private isPackageUnpacked(packagePath: string): IFuture<boolean> {
125+
return (() => {
126+
return this.$fs.getFsStats(packagePath).wait().isDirectory() &&
127+
helpers.enumerateFilesInDirectorySync(packagePath).length > 1;
128+
}).future<boolean>()();
129+
}
130+
116131
private addToCacheCore(packageName: string, version: string): IFuture<void> {
117132
var future = new Future<void>();
118133
npm.commands["cache"].add(packageName, version, undefined, (err: Error, data: any) => {
@@ -150,14 +165,5 @@ export class NodePackageManager implements INodePackageManager {
150165
return this.versionsCache[packageName];
151166
}).future<string[]>()();
152167
}
153-
154-
private validateVersion(packageName: string, version: string): IFuture<void> {
155-
return (() => {
156-
var versions = this.getAvailableVersions(packageName).wait();
157-
if(!_.contains(versions, version)) {
158-
this.$errors.fail("Invalid version. Valid versions are: %s", helpers.formatListOfNames(versions, "and"));
159-
}
160-
}).future<void>()();
161-
}
162168
}
163169
$injector.register("npm", NodePackageManager);

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: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,20 @@ class AndroidProjectService implements IPlatformProjectService {
4747

4848
public createProject(projectRoot: string, frameworkDir: string): IFuture<void> {
4949
return (() => {
50+
this.$fs.ensureDirectoryExists(projectRoot).wait();
51+
5052
this.validateAndroidTarget(frameworkDir); // We need framework to be installed to validate android target so we can't call this method in validate()
5153

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

55-
paths = ".project AndroidManifest.xml project.properties".split(' ').map(p => path.join(frameworkDir, p));
56-
shell.cp("-f", paths, projectRoot);
58+
this.symlinkDirectory("assets", projectRoot, frameworkDir).wait();
59+
this.symlinkDirectory("libs", projectRoot, frameworkDir).wait();
60+
} else {
61+
this.copy(projectRoot, frameworkDir, "assets libs res", "-R").wait();
62+
this.copy(projectRoot, frameworkDir, ".project AndroidManifest.xml project.properties", "-f").wait();
63+
}
5764

5865
// Create src folder
5966
var packageName = this.$projectData.projectId;
@@ -124,6 +131,13 @@ class AndroidProjectService implements IPlatformProjectService {
124131
return [".jar", ".dat"];
125132
}
126133

134+
private copy(projectRoot: string, frameworkDir: string, files: string, cpArg: string): IFuture<void> {
135+
return (() => {
136+
var paths = files.split(' ').map(p => path.join(frameworkDir, p));
137+
shell.cp(cpArg, paths, projectRoot);
138+
}).future<void>()();
139+
}
140+
127141
private spawn(command: string, args: string[]): IFuture<void> {
128142
if (hostInfo.isWindows()) {
129143
args.unshift('/s', '/c', command);
@@ -238,5 +252,23 @@ class AndroidProjectService implements IPlatformProjectService {
238252
}
239253
}).future<void>()();
240254
}
255+
256+
private symlinkDirectory(directoryName: string, projectRoot: string, frameworkDir: string): IFuture<void> {
257+
return (() => {
258+
this.$fs.createDirectory(path.join(projectRoot, directoryName)).wait();
259+
var directoryContent = this.$fs.readDirectory(path.join(frameworkDir, directoryName)).wait();
260+
261+
_.each(directoryContent, (file: string) => {
262+
var sourceFilePath = path.join(frameworkDir, directoryName, file);
263+
var destinationFilePath = path.join(projectRoot, directoryName, file);
264+
if(this.$fs.getFsStats(sourceFilePath).wait().isFile()) {
265+
this.$fs.symlink(sourceFilePath, destinationFilePath).wait();
266+
} else {
267+
this.$fs.symlink(sourceFilePath, destinationFilePath, "dir").wait();
268+
}
269+
});
270+
271+
}).future<void>()();
272+
}
241273
}
242274
$injector.register("androidProjectService", AndroidProjectService);

lib/services/ios-project-service.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,22 @@ 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.ensureDirectoryExists(path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)).wait();
67+
var xcodeProjectName = util.format("%s.xcodeproj", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER);
68+
69+
shell.cp("-R", path.join(frameworkDir, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, "*"), path.join(projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER));
70+
shell.cp("-R", path.join(frameworkDir, xcodeProjectName), path.join(projectRoot));
71+
72+
var directoryContent = this.$fs.readDirectory(frameworkDir).wait();
73+
var frameworkFiles = _.difference(directoryContent, [IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, xcodeProjectName]);
74+
_.each(frameworkFiles, (file: string) => {
75+
this.$fs.symlink(path.join(frameworkDir, file), path.join(projectRoot, file)).wait();
76+
});
77+
78+
} else {
79+
shell.cp("-R", path.join(frameworkDir, "*"), projectRoot);
80+
}
6681
}).future<void>()();
6782
}
6883

lib/services/platform-service.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,11 @@ export class PlatformService implements IPlatformService {
9191
platformData.platformProjectService.createProject(platformData.projectRoot, frameworkDir).wait();
9292
var installedVersion = this.$fs.readJson(path.join(frameworkDir, "../", "package.json")).wait().version;
9393

94-
// Need to remove unneeded node_modules folder
95-
// One level up is the runtime module and one above is the node_modules folder.
96-
this.$fs.deleteDirectory(path.join(frameworkDir, "../../")).wait();
94+
if(options.frameworkPath && !options.symlink) {
95+
// Need to remove unneeded node_modules folder
96+
// One level up is the runtime module and one above is the node_modules folder.
97+
this.$fs.deleteDirectory(path.join(frameworkDir, "../../")).wait();
98+
}
9799

98100
platformData.platformProjectService.interpolateData(platformData.projectRoot).wait();
99101
platformData.platformProjectService.afterCreateProject(platformData.projectRoot).wait();
@@ -405,7 +407,7 @@ export class PlatformService implements IPlatformService {
405407

406408
// Add new framework files
407409
var newFrameworkFiles = this.getFrameworkFiles(platformData, newVersion).wait();
408-
var cacheDirectoryPath = this.getNpmCacheDirectoryCore(platformData.frameworkPackageName, newVersion).wait();
410+
var cacheDirectoryPath = this.getNpmCacheDirectoryCore(platformData.frameworkPackageName, newVersion);
409411
_.each(newFrameworkFiles, file => {
410412
shell.cp("-f", path.join(cacheDirectoryPath, file), path.join(platformData.projectRoot, file));
411413
});
@@ -433,7 +435,7 @@ export class PlatformService implements IPlatformService {
433435

434436
private getNpmCacheDirectory(packageName: string, version: string): IFuture<string> {
435437
return (() => {
436-
var npmCacheDirectoryPath = this.getNpmCacheDirectoryCore(packageName, version).wait();
438+
var npmCacheDirectoryPath = this.getNpmCacheDirectoryCore(packageName, version);
437439

438440
if(!this.$fs.exists(npmCacheDirectoryPath).wait()) {
439441
this.$npm.addToCache(packageName, version).wait();
@@ -443,11 +445,8 @@ export class PlatformService implements IPlatformService {
443445
}).future<string>()();
444446
}
445447

446-
private getNpmCacheDirectoryCore(packageName: string, version: string): IFuture<string> {
447-
return (() => {
448-
var npmCacheRoot = this.$npm.getCacheRootPath().wait();
449-
return path.join(npmCacheRoot, packageName, version, "package");
450-
}).future<string>()();
448+
private getNpmCacheDirectoryCore(packageName: string, version: string): string {
449+
return path.join(this.$npm.getCacheRootPath(), packageName, version, "package");
451450
}
452451
}
453452
$injector.register("platformService", PlatformService);

0 commit comments

Comments
 (0)