Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions docs/man_pages/project/configuration/platform-add.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,28 @@ platform add

Usage | Synopsis
------|-------
Android latest runtime | `$ tns platform add android [--frameworkPath <File Path>] [--symlink] [--sdk <API Level>]`
Android selected runtime | `$ tns platform add android[@<Version>] [--frameworkPath <File Path>] [--symlink] [--sdk <API Level>]`
Android latest runtime | `$ tns platform add android [--frameworkPath <File Path>] [--symlink] [--sdk <API Level>] [--platformTemplate <Platform Template>]`
Android selected runtime | `$ tns platform add android[@<Version>] [--frameworkPath <File Path>] [--symlink] [--sdk <API Level>] [--platformTemplate <Platform Template>]`
<% if (isMacOS) { %>iOS latest runtime | `$ tns platform add ios [--frameworkPath <File Path>] [--symlink]`
iOS selected runtime | `$ tns platform add ios[@<Version>] [--frameworkPath <File Path>] [--symlink]`<% } %>
iOS selected runtime | `$ tns platform add ios[@<Version>] [--frameworkPath <File Path>] [--symlink] [--platformTemplate <Platform Template>]`<% } %>

Configures the current project to target the selected platform. <% if(isHtml) { %>When you add a target platform, the NativeScript CLI adds a corresponding platform-specific subdirectory under the platforms directory. This platform-specific directory contains the necessary files to let you build your project for the target platform.<% } %>
Configures the current project to target the selected platform. <% if(isHtml) { %>When you add a target platform, the NativeScript CLI adds a corresponding platform-specific subdirectory under the platforms directory. This platform-specific directory contains the necessary files to let you build your project for the target platform.<% } %>

### Options
* `--frameworkPath` - Sets the path to a NativeScript runtime for the specified platform that you want to use instead of the default runtime. If `--symlink` is specified, `<File Path>` must point to directory in which the runtime is already extracted. If `--symlink` is not specified, `<File Path>` must point to a valid npm package.
* `--frameworkPath` - Sets the path to a NativeScript runtime for the specified platform that you want to use instead of the default runtime. If `--symlink` is specified, `<File Path>` must point to directory in which the runtime is already extracted. If `--symlink` is not specified, `<File Path>` must point to a valid npm package.
* `--symlink` - Creates a symlink to a NativeScript runtime for the specified platform that you want to use instead of the default runtime. If `--frameworkPath` is specified, creates a symlink to the specified directory. If `--frameworkPath` is not specified, creates a symlink to platform runtime installed with your current version of NativeScript.
* `--sdk` - Sets the target Android SDK for the project.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add a line for --platformTemplate

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* `--platformTemplate` - Sets the Android/iOS template that will be used for the native application.

### Attributes
* `<API Level>` is a valid Android API level. For example: 17, 19, MNC.<% if(isHtml) { %> For a complete list of the Android API levels and their corresponding Android versions, click [here](http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#platform).<% } %>
* `<File Path>` is the complete path to a valid npm package or a directory that contains a NativeScript runtime for the selected platform.
* `<Version>` is any available version of the respective platform runtime published in npm. <% if(isHtml) { %>If `@<Version>` is not specified, the NativeScript CLI installs the latest stable runtime for the selected platform.
To list all available versions for Android, run `$ npm view tns-android versions`
To list only experimental versions for Android, run `$ npm view tns-android dist-tags`
To list all available versions for iOS, run `$ npm view tns-ios versions`
To list only experimental versions for iOS, run `$ npm view tns-ios dist-tags`
* `<Platform Template>` is a valid npm package, path to directory, .tgz or GitHub URL that contains native Android/iOS template.
* `<Version>` is any available version of the respective platform runtime published in npm. <% if(isHtml) { %>If `@<Version>` is not specified, the NativeScript CLI installs the latest stable runtime for the selected platform.
To list all available versions for Android, run `$ npm view tns-android versions`
To list only experimental versions for Android, run `$ npm view tns-android dist-tags`
To list all available versions for iOS, run `$ npm view tns-ios versions`
To list only experimental versions for iOS, run `$ npm view tns-ios dist-tags`

### Command Limitations

Expand Down
61 changes: 48 additions & 13 deletions lib/android-tools-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
private static ANDROID_TARGET_PREFIX = "android";
private static SUPPORTED_TARGETS = ["android-17", "android-18", "android-19", "android-21", "android-22", "android-23"];
private static MIN_REQUIRED_COMPILE_TARGET = 22;
private static REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=22";
private static REQUIRED_BUILD_TOOLS_RANGE_PREFIX = ">=23";
private static VERSION_REGEX = /((\d+\.){2}\d+)/;
private static MIN_JAVA_VERSION = "1.7.0";

Expand Down Expand Up @@ -156,30 +156,65 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
}).future<boolean>()();
}

public validateJavacVersion(installedJavaVersion: string, options?: {showWarningsAsErrors: boolean}): IFuture<boolean> {
public validateJava(javacVersion: string, options?: {showWarningsAsErrors: boolean}): IFuture<boolean> {
return ((): boolean => {
let hasProblemWithJavaVersion = false;
if(options) {
this.showWarningsAsErrors = options.showWarningsAsErrors;
}
let additionalMessage = "You will not be able to build your projects for Android." + EOL

let helpfulMessage = "You will not be able to build your projects for Android." + EOL
+ "To be able to build for Android, verify that you have installed The Java Development Kit (JDK) and configured it according to system requirements as" + EOL +
" described in https://github.com/NativeScript/nativescript-cli#system-requirements.";
let matchingVersion = (installedJavaVersion || "").match(AndroidToolsInfo.VERSION_REGEX);
if(matchingVersion && matchingVersion[1]) {
if(semver.lt(matchingVersion[1], AndroidToolsInfo.MIN_JAVA_VERSION)) {
hasProblemWithJavaVersion = true;
this.printMessage(`Javac version ${installedJavaVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION}.`, additionalMessage);

let hasProblemWithJavaHome = this.validateJavaHome(helpfulMessage).wait();
let hasProblemWithJavaVersion: boolean;
if(!hasProblemWithJavaHome) {
hasProblemWithJavaVersion = this.validateJavacVersion(javacVersion, helpfulMessage);
}

return hasProblemWithJavaHome || hasProblemWithJavaVersion;
}).future<boolean>()();
}

private validateJavaHome(helpfulMessage: string): IFuture<boolean> {
return ((): boolean => {
let hasProblemWithJavaHome = false;
let javaHome = process.env.JAVA_HOME;

if(javaHome) {
// validate jarsigner as it does not exist in JRE, but is mandatory for JDK and Android Runtime
let jarSigner = path.join(javaHome, "bin", "jarsigner");
let childProcessResult = this.$childProcess.spawnFromEvent(jarSigner, [], "close", {}, { throwError: false }).wait();
this.$logger.trace(`Result of calling jarsigner from path: ${jarSigner}:`, childProcessResult);
if(childProcessResult.stderr || childProcessResult.exitCode !== 0) {
hasProblemWithJavaHome = true;
this.printMessage("JAVA_HOME environment variable points to incorrect path. Make sure it points to the installation directory of JDK.", helpfulMessage);
}
} else {
hasProblemWithJavaVersion = true;
this.printMessage("Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable.", additionalMessage);
hasProblemWithJavaHome = true;
this.printMessage("JAVA_HOME environment variable is not set.", helpfulMessage);
}

return hasProblemWithJavaVersion;
return hasProblemWithJavaHome;
}).future<boolean>()();
}

private validateJavacVersion(installedJavaVersion: string, helpfulMessage: string): boolean {
let hasProblemWithJavaVersion = false;
let matchingVersion = (installedJavaVersion || "").match(AndroidToolsInfo.VERSION_REGEX);
if(matchingVersion && matchingVersion[1]) {
if(semver.lt(matchingVersion[1], AndroidToolsInfo.MIN_JAVA_VERSION)) {
hasProblemWithJavaVersion = true;
this.printMessage(`Javac version ${installedJavaVersion} is not supported. You have to install at least ${AndroidToolsInfo.MIN_JAVA_VERSION}.`, helpfulMessage);
}
} else {
hasProblemWithJavaVersion = true;
this.printMessage("Error executing command 'javac'. Make sure you have installed The Java Development Kit (JDK) and set JAVA_HOME environment variable.", helpfulMessage);
}

return hasProblemWithJavaVersion;
}

public getPathToAdbFromAndroidHome(): IFuture<string> {
return (() => {
if(this.androidHome) {
Expand Down Expand Up @@ -278,7 +313,7 @@ export class AndroidToolsInfo implements IAndroidToolsInfo {
selectedVersion = _.find(subDirs, dir => dir.indexOf(version) !== -1);
}
}

this.$logger.trace("Selected version is: ", selectedVersion);
return selectedVersion;
}).future<string>()();
}
Expand Down
7 changes: 4 additions & 3 deletions lib/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ interface IOptions extends ICommonOptions {
port: Number;
copyTo: string;
baseConfig: string;
platformTemplate: string;
}

interface IInitService {
Expand Down Expand Up @@ -160,12 +161,12 @@ interface IAndroidToolsInfo {
validateInfo(options?: {showWarningsAsErrors: boolean, validateTargetSdk: boolean}): IFuture<boolean>;

/**
* Validates the information about required JAVA version.
* @param {string} installedJavaVersion The JAVA version that will be checked.
* Validates the information about required JAVA version and JAVA_HOME.
* @param {string} javacVersion The JAVA version that will be checked.
* @param {any} options Defines if the warning messages should treated as error.
* @return {boolean} True if there are detected issues, false otherwise.
*/
validateJavacVersion(installedJavaVersion: string, options?: {showWarningsAsErrors: boolean}): IFuture<boolean>;
validateJava(javacVersion: string, options?: {showWarningsAsErrors: boolean}): IFuture<boolean>;

/**
* Returns the path to `android` executable. It should be `$ANDROID_HOME/tools/android`.
Expand Down
2 changes: 1 addition & 1 deletion lib/definitions/project.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ interface IiOSBuildConfig extends IBuildConfig {
interface IPlatformProjectService {
platformData: IPlatformData;
validate(): IFuture<void>;
createProject(frameworkDir: string, frameworkVersion: string): IFuture<void>;
createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture<void>;
interpolateData(): IFuture<void>;
interpolateConfigurationFile(configurationFilePath?: string): IFuture<void>;
afterCreateProject(projectRoot: string): IFuture<void>;
Expand Down
1 change: 1 addition & 0 deletions lib/nativescript-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as fiber from "fibers";
import Future = require("fibers/future");
import * as shelljs from "shelljs";
shelljs.config.silent = true;
shelljs.config.fatal = true;
import {installUncaughtExceptionListener} from "./common/errors";
installUncaughtExceptionListener(process.exit);

Expand Down
3 changes: 2 additions & 1 deletion lib/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export class Options extends commonOptionsLibPath.OptionsBase {
compileSdk: {type: OptionType.Number },
port: { type: OptionType.Number },
copyTo: { type: OptionType.String },
baseConfig: { type: OptionType.String }
baseConfig: { type: OptionType.String },
platformTemplate: { type: OptionType.String }
},
path.join($hostInfo.isWindows ? process.env.AppData : path.join(osenv.home(), ".local/share"), ".nativescript-cli"),
$errors, $staticConfig);
Expand Down
12 changes: 9 additions & 3 deletions lib/services/android-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject

// this call will fail in case `android` is not set correctly.
this.$androidToolsInfo.getPathToAndroidExecutable({showWarningsAsErrors: true}).wait();
this.$androidToolsInfo.validateJavacVersion(this.$sysInfo.getSysInfo(path.join(__dirname, "..", "..", "package.json")).wait().javacVersion, {showWarningsAsErrors: true}).wait();
this.$androidToolsInfo.validateJava(this.$sysInfo.getSysInfo(path.join(__dirname, "..", "..", "package.json")).wait().javacVersion, {showWarningsAsErrors: true}).wait();
}).future<void>()();
}

public createProject(frameworkDir: string, frameworkVersion: string): IFuture<void> {
public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture<void> {
return (() => {
if(semver.lt(frameworkVersion, AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE)) {
this.$errors.failWithoutHelp(`The NativeScript CLI requires Android runtime ${AndroidProjectService.MIN_RUNTIME_VERSION_WITH_GRADLE} or later to work properly.`);
Expand All @@ -111,7 +111,13 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
}

// These files and directories should not be symlinked as CLI is modifying them and we'll change the original values as well.
this.copy(this.platformData.projectRoot, frameworkDir, "src", "-R");
if(pathToTemplate) {
let mainPath = path.join(this.platformData.projectRoot, "src", "main");
this.$fs.createDirectory(mainPath).wait();
shell.cp("-R", path.join(path.resolve(pathToTemplate), "*"), mainPath);
} else {
this.copy(this.platformData.projectRoot, frameworkDir, "src", "-R");
}
this.copy(this.platformData.projectRoot, frameworkDir, "build.gradle settings.gradle gradle.properties", "-f");

if (this.useGradleWrapper(frameworkDir)) {
Expand Down
2 changes: 1 addition & 1 deletion lib/services/doctor-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class DoctorService implements IDoctorService {
}

let androidToolsIssues = this.$androidToolsInfo.validateInfo().wait();
let javaVersionIssue = this.$androidToolsInfo.validateJavacVersion(sysInfo.javacVersion).wait();
let javaVersionIssue = this.$androidToolsInfo.validateJava(sysInfo.javacVersion).wait();
return result || androidToolsIssues || javaVersionIssue;
}

Expand Down
11 changes: 8 additions & 3 deletions lib/services/ios-project-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,16 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
}).future<void>()();
}

public createProject(frameworkDir: string, frameworkVersion: string): IFuture<void> {
public createProject(frameworkDir: string, frameworkVersion: string, pathToTemplate?: string): IFuture<void> {
return (() => {
this.$fs.ensureDirectoryExists(path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER)).wait();
if(this.$options.symlink) {
if(pathToTemplate) {
// Copy everything except the template from the runtime
this.$fs.readDirectory(frameworkDir).wait()
.filter(dirName => dirName.indexOf(IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER) === -1)
.forEach(dirName => shell.cp("-R", path.join(frameworkDir, dirName), this.platformData.projectRoot));
shell.cp("-rf", path.join(pathToTemplate, "*"), this.platformData.projectRoot);
} else if(this.$options.symlink) {
let xcodeProjectName = util.format("%s.xcodeproj", IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER);

shell.cp("-R", path.join(frameworkDir, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER, "*"), path.join(this.platformData.projectRoot, IOSProjectService.IOS_PROJECT_NAME_PLACEHOLDER));
Expand All @@ -118,7 +124,6 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ
_.each(frameworkFiles, (file: string) => {
this.$fs.symlink(path.join(frameworkDir, file), path.join(this.platformData.projectRoot, file)).wait();
});

} else {
shell.cp("-R", path.join(frameworkDir, "*"), this.platformData.projectRoot);
}
Expand Down
50 changes: 46 additions & 4 deletions lib/services/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import * as helpers from "../common/helpers";
import * as semver from "semver";
import * as minimatch from "minimatch";
import Future = require("fibers/future");
import * as temp from "temp";
temp.track();
let clui = require("clui");

export class PlatformService implements IPlatformService {
Expand All @@ -29,7 +31,8 @@ export class PlatformService implements IPlatformService {
private $projectFilesManager: IProjectFilesManager,
private $mobileHelper: Mobile.IMobileHelper,
private $hostInfo: IHostInfo,
private $xmlValidator: IXmlValidator) { }
private $xmlValidator: IXmlValidator,
private $npm: INodePackageManager) { }

public addPlatforms(platforms: string[]): IFuture<void> {
return (() => {
Expand Down Expand Up @@ -114,7 +117,10 @@ export class PlatformService implements IPlatformService {
}

let sourceFrameworkDir = isFrameworkPathDirectory && this.$options.symlink ? path.join(this.$options.frameworkPath, "framework") : frameworkDir;
platformData.platformProjectService.createProject(path.resolve(sourceFrameworkDir), installedVersion).wait();
this.$projectDataService.initialize(this.$projectData.projectDir);
let customTemplateOptions = this.getPathToPlatformTemplate(this.$options.platformTemplate, platformData.frameworkPackageName).wait();
let pathToTemplate = customTemplateOptions && customTemplateOptions.pathToTemplate;
platformData.platformProjectService.createProject(path.resolve(sourceFrameworkDir), installedVersion, pathToTemplate).wait();

if(isFrameworkPathDirectory || isFrameworkPathNotSymlinkedFile) {
// Need to remove unneeded node_modules folder
Expand All @@ -132,12 +138,48 @@ export class PlatformService implements IPlatformService {
this.$fs.copyFile(newConfigFile, platformData.configurationFilePath).wait();
}

this.$projectDataService.initialize(this.$projectData.projectDir);
this.$projectDataService.setValue(platformData.frameworkPackageName, {version: installedVersion}).wait();
let frameworkPackageNameData: any = {version: installedVersion};
if(customTemplateOptions) {
frameworkPackageNameData.template = customTemplateOptions.selectedTemplate;
}
this.$projectDataService.setValue(platformData.frameworkPackageName, frameworkPackageNameData).wait();

}).future<void>()();
}

private getPathToPlatformTemplate(selectedTemplate: string, frameworkPackageName: string): IFuture<any> {
return (() => {
if(!selectedTemplate) {
// read data from package.json's nativescript key
// check the nativescript.tns-<platform>.template value
let nativescriptPlatformData = this.$projectDataService.getValue(frameworkPackageName).wait();
selectedTemplate = nativescriptPlatformData && nativescriptPlatformData.template;
}

if(selectedTemplate) {
let tempDir = temp.mkdirSync("platform-template");
try {
/*
* Output of npm.install is array of arrays. For example:
* [ [ 'test-android-platform-template@0.0.1',
* 'C:\\Users\\<USER>~1\\AppData\\Local\\Temp\\1\\platform-template11627-15560-rm3ngx\\node_modules\\test-android-platform-template',
* undefined,
* undefined,
* '..\\..\\..\\android-platform-template' ] ]
* Project successfully created.
*/
let pathToTemplate = this.$npm.install(selectedTemplate, tempDir).wait()[0][1];
return { selectedTemplate, pathToTemplate };
} catch(err) {
this.$logger.trace("Error while trying to install specified template: ", err);
this.$errors.failWithoutHelp(`Unable to install platform template ${selectedTemplate}. Make sure the specified value is valid.`);
}
}

return null;
}).future<any>()();
}

public getInstalledPlatforms(): IFuture<string[]> {
return(() => {
if(!this.$fs.exists(this.$projectData.platformsDir).wait()) {
Expand Down
1 change: 1 addition & 0 deletions test/platform-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ function createTestInjector() {
testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities);
testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
testInjector.register("xmlValidator", XmlValidator);
testInjector.register("npm", {});

return testInjector;
}
Expand Down
1 change: 1 addition & 0 deletions test/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function createTestInjector() {
testInjector.register("mobilePlatformsCapabilities", MobilePlatformsCapabilities);
testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
testInjector.register("xmlValidator", XmlValidator);
testInjector.register("npm", {});

return testInjector;
}
Expand Down