Skip to content

Fix isValidNativeScript project #2630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 24, 2017
Merged

Conversation

rosen-vladimirov
Copy link
Contributor

@rosen-vladimirov rosen-vladimirov commented Mar 21, 2017

The isValidNativeScript project method is used in Fusion. However the implementation is not correct. Fix it to have correct behavior.
In order to fix it, modify projectData.initialize method - there's obsolete code for migration from .tnsproject to package.json - we do not need it anymore, so remove it.
Also fix a case where failing builds do not fail the build process (incorrect argument passed to childProcess.spawnFromEvent).

Update documentation for PublicAPI.

Also in case CLI's executed in non-interactive terminal and there's no DEVELOPMENT_TEAM set, tns build ios --for-device fails with message "Console is not interactive and no default action specified" which does not give any info to the users.
So in this case add more descriptive error message.

@rosen-vladimirov rosen-vladimirov self-assigned this Mar 21, 2017
@rosen-vladimirov rosen-vladimirov force-pushed the vladimirov/public-api-fixes branch from bc52ef9 to 6747e54 Compare March 21, 2017 18:39
let data: any = null;

if (this.$fs.exists(this.projectFilePath)) {
if (projectFilePath) {
Copy link
Contributor

Choose a reason for hiding this comment

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

how can this ever be undefined? Why did we remove the exists? It might just not be a valid project?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for pointing this, I've deleted it by mistake.

}

if (data) {
this.projectDir = projectDir;
this.projectName = this.$projectHelper.sanitizeName(path.basename(projectDir));
this.platformsDir = path.join(projectDir, constants.PLATFORMS_DIR_NAME);
Copy link
Contributor

@yyosifov yyosifov Mar 23, 2017

Choose a reason for hiding this comment

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

projectName/platformsDir/appDirectoryPath/appResourcesDirectoryPath seem like "Computed properties" for me - they all are coming from the projectDir. Why not initialize only the project dir, and create getters for these properties so that we don't hit a bug if sometime we miss updating one of the computed ones? Do we have such a practice?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like the idea, but there's a reason to set the properties here - we are accessing them in many, many places, so computing each of them every single time will require reading the directory, the package.json, etc. This will make a lot of I/O operations. I can cache the properties after first execution, but this will not work for Fusion, which is a long living process.
So I'll keep this code until I figure out a better way for handling this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think I didn't explain it correctly. What was my idea is that if you set the projectDir, all other properties - projectName, platformsDir, appDirectoryPath can be computed from the projectDir variable - without reading anything from the package.json

i.e. something like:

private _platformsDir: string; get platformsDir():boolean { if(!this._platformsDir) { this._platformsDir = path.join(this.projectDir, constants.PLATFORMS_DIR_NAME); } return this._platformsDir; }

so that we don't forget to initialize the platformsDir somewhere.

}

// This is the case when no project file found
this.$errors.fail("No project found at or above '%s' and neither was a --path specified.", projectDir || this.$options.path || path.resolve("."));
Copy link
Contributor

@yyosifov yyosifov Mar 23, 2017

Choose a reason for hiding this comment

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

shall we add trace here, too? What bothers me is the three "or" statement, which will hide information from us on what exactly is the value of all of them.

}

return this.$platformService.getLatestApplicationPackageForDevice(platformData).packageName;
return this.$platformService.getLatestApplicationPackageForDevice(platformData, { isReleaseBuild: buildConfig.release }).packageName;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think we can rename from isReleaseBuild to release so that it's more consistent with the IBuildConfig?

}

return this.$platformService.getLatestApplicationPackageForDevice(platformData).packageName;
return this.$platformService.getLatestApplicationPackageForDevice(platformData, { isReleaseBuild: buildConfig.release }).packageName;
Copy link
Contributor

@yyosifov yyosifov Mar 23, 2017

Choose a reason for hiding this comment

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

Do you think we can directly pass on the buildConfig property, so that we have it in handy in case we need other property in the future? This would be only convenient if we have easy way to access the BuildConfig or create it at handy

`${projectData.projectName}-release.apk`
],
getValidPackageNames: (buildOptions: { isReleaseBuild?: boolean, isForDevice?: boolean }): string[] => {
if (buildOptions.isReleaseBuild) {
Copy link
Contributor

@yyosifov yyosifov Mar 23, 2017

Choose a reason for hiding this comment

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

Can be rewritten as:

const buildMode = buildOptions.isReleaseBuild ? "release" : "debug"; // or constants
return [
  `${packageName}-${buildMode}.apk`,
  `${projectData.projectName}-${buildMode}.apk` 
];

@@ -262,7 +269,7 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
await this.spawn(gradleBin,
buildOptions,
{ stdio: buildConfig.buildOutputStdio || "inherit", cwd: this.getPlatformData(projectData).projectRoot },
{ emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: false });
{ emitOptions: { eventName: constants.BUILD_OUTPUT_EVENT_NAME }, throwError: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@@ -426,6 +433,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
}

public async cleanProject(projectRoot: string, options: string[], projectData: IProjectData): Promise<void> {
// In case options are not passed, we'll use the default ones for cleaning the project.
// In case we do not do this, we'll required android-23 (default in build.gradle) for cleaning the project.
options = options.length === 0 ? this.getBuildOptions({ release: false }, projectData) : options;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think it would be a good idea to "extend"/"merge" the options passed with the "default options" which the getBuildOptions returns? So that we don't end up with fewer options than we need?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact the options to cleanProject are passed only at a single place and they are the result of this.getBuildOptions({ release: false }, projectData), so instead of applying some merge mechanism, I've decided to remove the options from the method and always generate them here.

@@ -426,6 +433,10 @@ export class AndroidProjectService extends projectServiceBaseLib.PlatformProject
}

public async cleanProject(projectRoot: string, options: string[], projectData: IProjectData): Promise<void> {
// In case options are not passed, we'll use the default ones for cleaning the project.
// In case we do not do this, we'll required android-23 (default in build.gradle) for cleaning 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.

small typo: "we'll required" to "we'll require" I guess?

Copy link
Contributor

@Mitko-Kerezov Mitko-Kerezov left a comment

Choose a reason for hiding this comment

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

After fixing minor comments

@@ -143,28 +143,30 @@ interface IPlatformService extends NodeJS.EventEmitter {
/**
* Returns information about the latest built application for device in the current project.
* @param {IPlatformData} platformData Data describing the current platform.
* @param {any} buildOptions Defines if the build is for release configuration.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why any


/**
* Returns information about the latest built application for simulator in the current project.
* @param {IPlatformData} platformData Data describing the current platform.
* @param {any} buildOptions Defines if the build is for release configuration.
Copy link
Contributor

Choose a reason for hiding this comment

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

any again

data = fileContent[this.$staticConfig.CLIENT_NAME_KEY_IN_PROJECT_FILE];
} catch (err) {
this.$errors.fail({
formatStr: "The project file %s is corrupted." + EOL +
this.$errors.failWithoutHelp(
Copy link
Contributor

Choose a reason for hiding this comment

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

You can pass a single template string as an argument here instead of concatenating template strings with regular ones

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll use three template strings in order to keep the readability of the code and keep the formatting of the message.

let data: any = null;

if (this.$fs.exists(this.projectFilePath)) {
if (projectFilePath) {
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO it makes more sense to invert the if statement and just fail if projectFilePath isn't set. It'd make the code more readable I think, because right now the majority of the working code is inside the if statement

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would like to fail in case projectFilePath does not exist or in case there's no data in the file. There are two cases where we'll fail with the same error (that a project is not found), that's why I've left the throw statement at the end of the method.

private $fs: IFileSystem) {
}

public get currentChanges(): IProjectChangesInfo {
return this._changesInfo;
}

public checkForChanges(platform: string, projectData: IProjectData): IProjectChangesInfo {
public checkForChanges(platform: string, projectData: IProjectData, buildOptions: IProjectChangesOptions): IProjectChangesInfo {
Copy link
Contributor

Choose a reason for hiding this comment

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

Weird naming for me: "buildOptions for ProjectChangesOptions"

@@ -73,11 +73,10 @@ export class ProjectService implements IProjectService {
public isValidNativeScriptProject(pathToProject?: string): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering, do we want to return some errors if we can identify. I mean - OK, it's not a valid NativeScript project but what's the actual issue? Can the user repair his project to be valid - in case he can, I'd suggest we somehow give him a hint what needs to be fixed? What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method is created for Fusion, where when the user selects a folder, we try to check if this is a valid project.
All NativeScript CLI users will see the message from initializeProjectData method.

@rosen-vladimirov rosen-vladimirov force-pushed the vladimirov/public-api-fixes branch from 6ed8cfd to aca4275 Compare March 23, 2017 20:55
@rosen-vladimirov
Copy link
Contributor Author

Ping @yyosifov , @Mitko-Kerezov - comments are fixed

Copy link
Contributor

@Mitko-Kerezov Mitko-Kerezov left a comment

Choose a reason for hiding this comment

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

After fixing minor cosmetic comment

@@ -93,10 +93,10 @@ class IOSDebugService implements IDebugService {
}
}

private async emulatorDebugBrk(projectData: IProjectData, shouldBreak?: boolean): Promise<void> {
private async emulatorDebugBrk(projectData: IProjectData, buildConfig: IBuildConfig, shouldBreak?: boolean, ): Promise<void> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Gratuitous comma at the end of args list

rosen-vladimirov and others added 5 commits March 24, 2017 10:40
The isValidNativeScript project method is used in Fusion. However the implementation is not correct. Fix it to have correct behavior.
In order to fix it, modify projectData.initialize method - there's obsolete code for migration from .tnsproject to package.json - we do not need it anymore, so remove it.
Also fix a case where failing builds do not fail the build process (incorrect argument passed to childProcess.spawnFromEvent).

Update documentation for PublicAPI.
Also in case CLI's executed in non-interactive terminal and there's no DEVELOPMENT_TEAM set, tns build ios --for-device fails with message "Console is not interactive and no default action specified" which does not give any info to the users.
So in this case add more descriptive error message.
When we try to get the latest application package for device/emulator, we check the build output directory and get latest available .apk/.ipa. However this is not always correct as sometimes we do not build the app. Consider the following case:
* tns build android --release
* tns build android
* tns build android --release

At the last point, the build will not be executed, as there are no changes. However the last built .apk is from the debug build (we have release .apk, but it's older). So in case we try to get the last build output from last operation, CLI will return the debug.apk

Fix this by checking the current build configuration and get the latest result by using it. For iOS respect the expected output - is it for device or for simulator as the output is different.
When CLI is required as library, the `$options` dependency is not populated. However the projectChangesService relies on it in order to determine if the project should be prepared/built.
When CLI is required as library and you change only the build configuration (debug/release), the project is not rebuilt. However when you use the CLI and try the same, a new full build is triggered.
Fix this by parsing required boolean flags to projectChangesService and exclude `$options` from its implementation. This way local builds will work in the same manner both from command line and when required as library.

Steps to reproduce the problem:
* use CLI as lib
* create local build for android in debug configuration
* create local build for android in release configuration - you'll notice gradle clean is not called at all and also in the `<project dir>/platforms/android/build/outputs/apks/` there are both debug and release apks. This should never happen when changing configurations.
Pass the whole buildConfig object when getting the path to the last build output. This required changes in debug services and TestExecutionService.
Also change the args of cleanProject method - the `options` passed to `gradle` are now determined in the method itself instead of passing them from the caller.
This way we'll not require "android-23" (the default one set in build.gradle) when we miss to pass the required options.
@rosen-vladimirov rosen-vladimirov force-pushed the vladimirov/public-api-fixes branch from aca4275 to c5b1129 Compare March 24, 2017 08:41
@rosen-vladimirov rosen-vladimirov merged commit ddd8328 into master Mar 24, 2017
@rosen-vladimirov rosen-vladimirov deleted the vladimirov/public-api-fixes branch March 24, 2017 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants