From 874bb71c84d6a85fc9590fff26eeca11b0d74435 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Mon, 11 May 2020 09:09:50 -0700 Subject: [PATCH 01/28] Auto-linking for native modules (source-based) * Updated `npx react-native config` to create the needed json for auto-linking * New `npx react-native autolink-windows` command to perform auto-linking * Auto-linking occurs automatically during `npx react-native run-windows` * New Auto-linking targets and props so CLI apps throw a warning if auto-linking should be run * New targets and props for native modules to consume Closes #2853 --- vnext/PropertySheets/Autolink.props | 14 + vnext/PropertySheets/Autolink.targets | 10 + .../Microsoft.ReactNative.Uwp.CSharpApp.props | 1 + ...icrosoft.ReactNative.Uwp.CSharpApp.targets | 1 + ...crosoft.ReactNative.Uwp.CSharpModule.props | 10 + ...osoft.ReactNative.Uwp.CSharpModule.targets | 21 + .../Microsoft.ReactNative.Uwp.CppApp.props | 1 + .../Microsoft.ReactNative.Uwp.CppApp.targets | 1 + .../Microsoft.ReactNative.Uwp.CppModule.props | 11 + ...icrosoft.ReactNative.Uwp.CppModule.targets | 17 + .../utils => Scripts}/VSProjectUtils.ps1 | 2 +- vnext/local-cli/config/configUtils.js | 144 +++++++ vnext/local-cli/config/dependencyConfig.js | 177 ++++---- vnext/local-cli/config/projectConfig.js | 103 ++--- .../cpp/src/AutolinkedNativeModules.g.h | 7 +- .../cs/src/AutolinkedNativeModules.g.cs | 3 +- vnext/local-cli/runWindows/runWindows.js | 14 +- vnext/local-cli/runWindows/utils/autolink.js | 392 +++++++++++++++--- vnext/local-cli/runWindows/utils/vstools.js | 176 ++++++++ vnext/react-native.config.js | 1 + 20 files changed, 899 insertions(+), 207 deletions(-) create mode 100644 vnext/PropertySheets/Autolink.props create mode 100644 vnext/PropertySheets/Autolink.targets create mode 100644 vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpModule.props create mode 100644 vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpModule.targets create mode 100644 vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppModule.props create mode 100644 vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppModule.targets rename vnext/{local-cli/runWindows/utils => Scripts}/VSProjectUtils.ps1 (99%) create mode 100644 vnext/local-cli/config/configUtils.js create mode 100644 vnext/local-cli/runWindows/utils/vstools.js diff --git a/vnext/PropertySheets/Autolink.props b/vnext/PropertySheets/Autolink.props new file mode 100644 index 00000000000..18a248ce190 --- /dev/null +++ b/vnext/PropertySheets/Autolink.props @@ -0,0 +1,14 @@ + + + + + + npx react-native autolink-windows + --check + $([MSBuild]::GetDirectoryNameOfFileAbove($(ProjectDir), 'package.json')) + + + diff --git a/vnext/PropertySheets/Autolink.targets b/vnext/PropertySheets/Autolink.targets new file mode 100644 index 00000000000..94f88bba2f9 --- /dev/null +++ b/vnext/PropertySheets/Autolink.targets @@ -0,0 +1,10 @@ + + + + + + + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.props b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.props index 507b242e185..90b05d187e5 100644 --- a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.props +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.props @@ -8,4 +8,5 @@ $([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)\..\..'))\ + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets index ce5f4fe3c72..1359db9a4df 100644 --- a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets @@ -18,6 +18,7 @@ + + + + + $([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)\..\..'))\ + + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpModule.targets b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpModule.targets new file mode 100644 index 00000000000..80f01c321c5 --- /dev/null +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpModule.targets @@ -0,0 +1,21 @@ + + + + + + {f7d32bd0-2749-483e-9a0d-1635ef7e3136} + Microsoft.ReactNative + + + {F2824844-CE15-4242-9420-308923CD76C3} + Microsoft.ReactNative.Managed + + + 2.3.191129002 + + + + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.props b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.props index fcc4b9abb6d..7a123a856b4 100644 --- a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.props +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.props @@ -9,4 +9,5 @@ false + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets index 9aad8acc30f..c05073732ec 100644 --- a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets @@ -13,6 +13,7 @@ + + + + + $([MSBuild]::NormalizePath('$(MSBuildThisFileDirectory)\..\..'))\ + false + + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppModule.targets b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppModule.targets new file mode 100644 index 00000000000..7ab4210f528 --- /dev/null +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppModule.targets @@ -0,0 +1,17 @@ + + + + + + + + + {f7d32bd0-2749-483e-9a0d-1635ef7e3136} + false + + + + diff --git a/vnext/local-cli/runWindows/utils/VSProjectUtils.ps1 b/vnext/Scripts/VSProjectUtils.ps1 similarity index 99% rename from vnext/local-cli/runWindows/utils/VSProjectUtils.ps1 rename to vnext/Scripts/VSProjectUtils.ps1 index c80a798a0a9..0abdabcfe68 100644 --- a/vnext/local-cli/runWindows/utils/VSProjectUtils.ps1 +++ b/vnext/Scripts/VSProjectUtils.ps1 @@ -124,7 +124,7 @@ function Add-PackagesConfigXml { [bool] $CheckConfig = $True ) - $packageXml = Load-PackagesConfigXml -PackagesConfigPath $PackagesConfigPath + $packageXml = Load-PackagesConfigXml -PackagesConfigPath $PackagesConfigPath -CheckConfig $CheckConfig $existingPackageNode = $packageXml.selectSingleNode("packages/package[@id=""$PackageName""]") diff --git a/vnext/local-cli/config/configUtils.js b/vnext/local-cli/config/configUtils.js new file mode 100644 index 00000000000..e0d4e60607b --- /dev/null +++ b/vnext/local-cli/config/configUtils.js @@ -0,0 +1,144 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +// @ts-check + +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); + +const xmldoc = require('xmldoc'); + +// find VS files matching the pattern +function findFiles(folder, filenamePattern) { + const files = glob.sync(path.join('**', filenamePattern), { + cwd: folder, + ignore: [ + 'node_modules/**', + '**/Debug/**', + '**/Release/**', + '**/Generated Files/**', + '**/packages/**', + ], + }); + + return files; +} + +// finds the windows folder if present +function findWindowsFolder(folder) { + const winDir = 'windows'; + const joinedDir = path.join(folder, winDir); + if (fs.existsSync(joinedDir)) { + return joinedDir; + } + + return null; +} + +// find the visual studio solution file +function findSolutionFile(winFolder, packageJson = {}) { + if (packageJson.name) { + const solutionName = packageJson.name; + + // First look for a solution named solutionName.sln + const slnFile = findFiles(winFolder, solutionName + '.sln')[0]; + + if (slnFile) { + return path.join(winFolder, slnFile); + } + } + + // Next look for any solution *.sln + const allSln = findFiles(winFolder, '*.sln'); + + if (allSln.length > 0) { + return path.join(winFolder, allSln[0]); + } + + return null; +} + +// find the visual studio project file +function findProjectFile(winFolder, packageJson = {}) { + if (packageJson.name) { + const projectName = packageJson.name; + + // First look for a project named projectName.vcxproj + const cppProj = findFiles(winFolder, projectName + '.vcxproj')[0]; + + if (cppProj) { + return path.join(winFolder, cppProj); + } + + // Next look for a project named projectName.csproj + const csProj = findFiles(winFolder, projectName + '.csproj')[0]; + + if (csProj) { + return path.join(winFolder, csProj); + } + } + + // Next look for any project *.vcxproj + const allCppProj = findFiles(winFolder, '*.vcxproj'); + + if (allCppProj.length > 0) { + return path.join(winFolder, allCppProj[0]); + } + + // Next look for any project *.csproj + const allCsProj = findFiles(winFolder, '*.csproj'); + + if (allCsProj.length > 0) { + return path.join(winFolder, allCsProj[0]); + } + + return null; +} + +function getProjectLanguage(projectPath) { + if (projectPath.endsWith('.vcxproj')) { + return 'cpp'; + } else if (projectPath.endsWith('.csproj')) { + return 'cs'; + } + return null; +} + +// read visual studio project file which is actually a XML doc +function readProjectFile(projectPath) { + return new xmldoc.XmlDocument(fs.readFileSync(projectPath, 'utf8')); +} + +function findProperty(projectXml, propertyName) { + return projectXml.valueWithPath('PropertyGroup.' + propertyName); +} + +function getProjectName(projectXml) { + return ( + findProperty(projectXml, 'ProjectName') || + findProperty(projectXml, 'AssemblyName') + ); +} + +function getProjectNamespace(projectXml) { + return findProperty(projectXml, 'RootNamespace'); +} + +function getProjectGuid(projectXml) { + return findProperty(projectXml, 'ProjectGuid'); +} + +module.exports = { + findFiles: findFiles, + findWindowsFolder: findWindowsFolder, + findSolutionFile: findSolutionFile, + findProjectFile: findProjectFile, + getProjectLanguage: getProjectLanguage, + readProjectFile: readProjectFile, + getProjectName: getProjectName, + getProjectNamespace: getProjectNamespace, + getProjectGuid: getProjectGuid, +}; diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 40e4d5a8a9c..771f4b0cfcb 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -1,102 +1,117 @@ -const fs = require('fs'); +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +// @ts-check + const path = require('path'); -const glob = require('glob'); -const xmldoc = require('xmldoc'); -function dependencyConfigWindows(folder, userConfig = {}) { - const sourceDir = userConfig.sourceDir || findWindowsAppFolder(folder); +const configUtils = require('./configUtils.js'); + +/* + +Schema for dependencies: + +{ + folder: '', // absolute path to the module root, ex: c:\path\to\node_modules\module-name + sourceDir: '', // relative path to the windows implementation folder, ex: windows + solutionFile: '', // relative path to the solution, ex: ModuleName.sln + projects: [ // array of projects to be built that include modules + { + projectFile: '', // relative path to the project, ex: ProjectName\ProjectName.vcxproj + projectName: '', // name of the project + projectLang: '', // language of the project, cpp or cs + projectGuid: '', // project identifier + cppHeaders: [], // array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' + cppPackageProviders: [], // array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' + csNamespaces: [], // array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' + csPackageProviders: [], // array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' + }, + ], + additionalProjects: [ // array of (dependency) projects that must be built, but do not contain modules + { + projectFile: '', // relative path to the project, ex: ProjectName\ProjectName.vcxproj + projectName: '', // name of the project + projectLang: '', // language of the project, cpp or cs + projectGuid: '', // project identifier + }, + ], + packages: [ // array of nuget packages that include modules + { + packageName: '', // name of the nuget package to install + packageVersion: '', // version of the nuget package to install + cppHeaders: [], // array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' + cppPackageProviders: [], // array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' + csNamespaces: [], // array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' + csPackageProviders: [], // array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' + }, + ], +} - if (!sourceDir) { - return null; - } +*/ - var packageName = null; - const packageIDL = findPackageProviderIDL(sourceDir); - if (packageIDL){ - packageName = parsePackageIDLFile(packageIDL); - } - var cppProjFile = null; - var csProjectFile = null; - if (packageName) - { - cppProjFile = findCppProject(sourceDir, packageName); - csProjectFile = findCSProject(sourceDir,packageName); +function dependencyConfigWindows(folder, userConfig = {}) { + if (userConfig.sourceDir) { + return { + folder, + sourceDir: userConfig.sourceDir, + solutionFile: userConfig.solutionFile, + projects: userConfig.projects || [], + additionalProjects: userConfig.additionalProjects || [], + packages: userConfig.packages || [], + }; } - var projGUID = null; - if (cppProjFile) - { - const proj = readProject(cppProjFile); - var groupNode = proj.childNamed('PropertyGroup'); - if (groupNode){ - var nameNode = groupNode.childNamed('ProjectGuid'); - if (nameNode){ - projGUID = nameNode.val; - } - } - } + const sourceDir = configUtils.findWindowsFolder(folder); - return { - sourceDir, - packageIDL, - packageName, - cppProjFile, - csProjectFile, - projGUID, - }; -} - -function findWindowsAppFolder(folder) { - const winDir = 'windows'; - const joinedDir = path.join(folder, winDir); - if (fs.existsSync(joinedDir)) { - return joinedDir; + if (!sourceDir) { + return null; } - return null; -} + const packageJson = require(path.join(folder, 'package.json')); -// assumption is every cpp native module will have a ReactPackageProvider.idl defined -function findPackageProviderIDL(folder) { - const PackageIDLPath = glob.sync(path.join('**', 'ReactPackageProvider.idl'), { - cwd: folder, - ignore: ['node_modules/**', '**/Debug/**', '**/Release/**', 'Generated Files'], - })[0]; + const solutionFile = configUtils.findSolutionFile(sourceDir, packageJson); - return PackageIDLPath ? path.join(folder, PackageIDLPath) : null; -} + const projectFile = configUtils.findProjectFile(sourceDir, packageJson); -// look for packagename 'XYZ' in string 'namesapce XYZ {' -function parsePackageIDLFile(packageIDL) { - const buf = fs.readFileSync(packageIDL, 'utf8'); - const indexofNameSpace = buf.indexOf('namespace') + 9; - const indexofBracket = buf.indexOf('{'); - const packageName = buf.substring(indexofNameSpace, indexofBracket).replace(/\s+/g, ' ').trim(); + const projectLang = configUtils.getProjectLanguage(projectFile); - return packageName; -} + const projectXml = configUtils.readProjectFile(projectFile); -// read visual studio project file which is actually a XML doc -function readProject(projectPath) { - return new (xmldoc.XmlDocument)(fs.readFileSync(projectPath, 'utf8')); -} + const projectName = configUtils.getProjectName(projectXml); -function findCppProject(folder, projectName) { - const cppProj = glob.sync(path.join('**', projectName + '.vcxproj'), { - cwd: folder, - ignore: ['node_modules/**', '**/Debug/**', '**/Release/**', '**/Generated Files/**', '**/packages/**'], - })[0]; + const projectGuid = configUtils.getProjectGuid(projectXml); - return cppProj ? path.join(folder, cppProj) : null; -} + const projectNamespace = configUtils.getProjectNamespace(projectXml); + + const cppNamespace = projectNamespace.replace('.', '::'); + const csNamespace = projectNamespace.replace('::', '.'); -function findCSProject(folder, projectName) { - const cppProj = glob.sync(path.join('**', projectName + '.csproj'), { - cwd: folder, - ignore: ['node_modules/**', '**/Debug/**', '**/Release/**', '**/Generated Files/**', '**/packages/**'], - })[0]; + var cppHeaders = [`winrt/${csNamespace}.h`]; + var cppPackageProviders = [`${cppNamespace}::ReactPackageProvider`]; + var csNamespaces = [`${csNamespace}`]; + var csPackageProviders = [`${csNamespace}.ReactPackageProvider`]; - return cppProj ? path.join(folder, cppProj) : null; + return { + folder, + sourceDir: sourceDir.substr(folder.length + 1), + solutionFile: solutionFile.substr(sourceDir.length + 1), + projects: [ + { + projectFile: projectFile.substr(sourceDir.length + 1), + projectName, + projectLang, + projectGuid, + cppHeaders, + cppPackageProviders, + csNamespaces, + csPackageProviders, + }, + ], + additionalProjects: [], + packages: [], + }; } module.exports = { diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index 4569b84c414..d4d24b5ff40 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -1,66 +1,73 @@ -const fs = require('fs'); +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +// @ts-check + const path = require('path'); -const glob = require('glob'); -function projectConfigWindows(folder, userConfig = {}) { - const sourceDir = userConfig.sourceDir || findWindowsAppFolder(folder); +const configUtils = require('./configUtils.js'); - if (!sourceDir) { - return null; - } +/* - const projectSolution = findSolution(sourceDir); - if (projectSolution){ - var extension = path.extname(projectSolution); - var projectName = path.basename(projectSolution, extension); - } - const cppProjFile = findCppProject(sourceDir, projectName); - const csProjectFile = findCSProject(sourceDir,projectName); +Schema for app projects: - return { - sourceDir, - projectSolution, - projectName, - cppProjFile, - csProjectFile, - }; +{ + folder: '', // absolute path to the project root, ex: c:\path\project + sourceDir: '', // relative path to the windows implementation folder, ex: windows + solutionFile: '', // relative path to the solution, ex: ProjectName.sln + project: { + projectFile: '', // relative path to the project, ex: ProjectName\ProjectName.vcxproj + projectName: '', // name of the project + projectLang: '', // language of the project, cpp or cs + projectGuid: '', // project identifier + }, } -function findWindowsAppFolder(folder) { - const winDir = 'windows'; - const joinedDir = path.join(folder, winDir); - if (fs.existsSync(joinedDir)) { - return joinedDir; +*/ + +function projectConfigWindows(folder, userConfig = {}) { + if (userConfig.sourceDir) { + return { + folder, + sourceDir: userConfig.sourceDir, + solutionFile: userConfig.solutionFile, + project: userConfig.project, + }; } - return null; -} + const sourceDir = configUtils.findWindowsFolder(folder); -function findSolution(folder) { - const solutionPath = glob.sync(path.join('**', '*.sln'), { - cwd: folder, - ignore: ['node_modules/**', '**/Debug/**', '**/Release/**', 'Generated Files'], - })[0]; + if (!sourceDir) { + return null; + } - return solutionPath ? path.join(folder, solutionPath) : null; -} + const packageJson = require(path.join(folder, 'package.json')); -function findCppProject(folder, projectName) { - const cppProj = glob.sync(path.join('**', projectName + '.vcxproj'), { - cwd: folder, - ignore: ['node_modules/**', '**/Debug/**', '**/Release/**', '**/Generated Files/**', '**/packages/**'], - })[0]; + const solutionFile = configUtils.findSolutionFile(sourceDir, packageJson); - return cppProj ? path.join(folder, cppProj) : null; -} + const projectFile = configUtils.findProjectFile(sourceDir, packageJson); -function findCSProject(folder, projectName) { - const cppProj = glob.sync(path.join('**', projectName + '.csproj'), { - cwd: folder, - ignore: ['node_modules/**', '**/Debug/**', '**/Release/**', '**/Generated Files/**', '**/packages/**'], - })[0]; + const projectLang = configUtils.getProjectLanguage(projectFile); - return cppProj ? path.join(folder, cppProj) : null; + const projectXml = configUtils.readProjectFile(projectFile); + + const projectName = configUtils.getProjectName(projectXml); + + const projectGuid = configUtils.getProjectGuid(projectXml); + + return { + folder, + sourceDir: sourceDir.substr(folder.length + 1), + solutionFile: solutionFile.substr(sourceDir.length + 1), + project: { + projectFile: projectFile.substr(sourceDir.length + 1), + projectName, + projectLang, + projectGuid, + }, + }; } module.exports = { diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h index 6608d9c46d0..c5eff9d90b3 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h +++ b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h @@ -1,17 +1,14 @@ -// AutolinkedNativeModules.g.h -- contents generated by "react-native run-windows" - +// AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" +// clang-format off #include "pch.h" #pragma once -// clang-format off - namespace winrt::Microsoft::ReactNative { static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) { - } } diff --git a/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs b/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs index 4f311206b86..fd512bbb55e 100644 --- a/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs +++ b/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs @@ -1,4 +1,4 @@ -// AutolinkedNativeModules.g.cs -- contents generated by "react-native run-windows" +// AutolinkedNativeModules.g.cs contents generated by "react-native autolink-windows" using System.Collections.Generic; @@ -8,7 +8,6 @@ internal static class AutolinkedNativeModules { internal static void RegisterAutolinkedNativeModulePackages(IList packageProviders) { - } } } diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index bca3b328602..5b47abe4779 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -12,6 +12,7 @@ const {newError, newInfo} = require('./utils/commandWithProgress'); const info = require('./utils/info'); const msbuildtools = require('./utils/msbuildtools'); const autolink = require('./utils/autolink'); + const chalk = require('chalk'); function ExitProcessWithError(loggingWasEnabled) { @@ -51,8 +52,9 @@ async function runWindows(config, args, options) { const slnFile = options.sln || build.getSolutionFile(options); if (options.autolink) { - autolink.updateAutoLink(verbose); + await autolink.func(null, null, {logging: options.logging}); } + if (options.build) { if (!slnFile) { newError( @@ -200,6 +202,11 @@ module.exports = { description: 'Do not launch the app after deployment', default: false, }, + { + command: '--no-autolink', + description: 'Do not run autolinking', + default: false, + }, { command: '--no-build', description: 'Do not build the solution', @@ -225,11 +232,6 @@ module.exports = { description: 'Dump environment information', default: false, }, - { - command: '--autolink', - description: 'Auto link native modules', - default: false, - }, { command: '--direct-debugging [number]', description: 'Enable direct debugging on specified port', diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index d69ba539849..1cd22879442 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -6,95 +6,359 @@ // @ts-check const execSync = require('child_process').execSync; -const path = require('path'); const fs = require('fs'); +const path = require('path'); +const chalk = require('chalk'); + +const {newSpinner} = require('./commandWithProgress'); +const vstools = require('./vstools'); + +function verboseMessage(message, verbose) { + if (verbose) { + console.log(message); + } +} + +function updateFile(filePath, expectedContents, verbose, checkMode) { + const fileName = chalk.bold(path.basename(filePath)); + verboseMessage(`Reading ${fileName}...`, verbose); + const actualContents = fs.existsSync(filePath) + ? fs + .readFileSync(filePath) + .toString() + .replace(/\r\n/g, '\n') + : ''; + + const contentsChanged = expectedContents !== actualContents; + + if (contentsChanged) { + verboseMessage(chalk.yellow(`${fileName} needs to be updated.`), verbose); + if (!checkMode) { + verboseMessage(`Writing ${fileName}...`, verbose); + fs.writeFileSync(filePath, expectedContents.replace(/\n/g, '\r\n'), { + encoding: 'utf8', + flag: 'w', + }); + } + } else { + verboseMessage(`No changes to ${fileName}.`, verbose); + } + + return contentsChanged; +} + +function ExitProcessWithStatusCode(statusCode, loggingWasEnabled) { + if (!loggingWasEnabled && statusCode !== 0) { + console.log( + `Error: Re-run the command with ${chalk.bold( + '--logging', + )} for more information.`, + ); + } + process.exit(statusCode); +} + +async function updateAutoLink(config, args, options) { + const verbose = options.logging; + + const checkMode = options.check; + + var changesNecessary = false; + + const spinner = newSpinner( + checkMode ? 'Checking auto-linked files...' : 'Auto-linking...', + ); + + verboseMessage('', verbose); -function updateAutoLink(verbose) { const execString = 'react-native config'; let output; try { - console.log('Running react-native config...'); + verboseMessage(`Running ${chalk.bold('react-native config')}...`, verbose); + output = execSync(execString).toString(); - if (verbose) { - console.log(output); - } - const config = JSON.parse(output); - const windowsPlatformConfig = config.project.windows; - const cppProjFile = windowsPlatformConfig - ? windowsPlatformConfig.cppProjFile - : null; - if (cppProjFile == null) { - console.log( - 'AutoLink currently is only supported on C++/WinRT main project.', - ); - return; + verboseMessage(output, verbose); + + verboseMessage('Parsing output...', verbose); + + const rnConfig = JSON.parse(output); + const windowsAppConfig = rnConfig.project.windows; + + if (!windowsAppConfig) { + throw 'Windows auto-link only supported on windows app projects.'; } - console.log(`cppProjFile: ${cppProjFile}`); - const sourceDir = config.project.windows.sourceDir; - console.log(`sourceDir: ${sourceDir}`); + verboseMessage('Found Windows app project, parsing...', verbose); - const projectName = config.project.windows.projectName; - console.log(`projectName: ${projectName}`); + const solutionFile = path.join( + windowsAppConfig.folder, + windowsAppConfig.sourceDir, + windowsAppConfig.solutionFile, + ); - //#1. update nativeModules.g.h - const generatedHeader = path.join( - sourceDir, - projectName, - 'AutolinkedNativeModules.g.h', + const projectFile = path.join( + windowsAppConfig.folder, + windowsAppConfig.sourceDir, + windowsAppConfig.project.projectFile, ); - if (!fs.existsSync(generatedHeader)) { - console.log( - 'AutoLink can not locate generated header file: {generatedHeader}', + + const projectDir = path.dirname(projectFile); + const projectLang = windowsAppConfig.project.projectLang; + + verboseMessage('Parsing dependencies...', verbose); + + const dependencies = rnConfig.dependencies; + + let windowsDependencies = {}; + + for (const dependencyName in dependencies) { + const windowsDependency = dependencies[dependencyName].platforms.windows; + + if (windowsDependency) { + verboseMessage( + `Found dependency ${chalk.bold(dependencyName)}.`, + verbose, + ); + windowsDependencies[dependencyName] = windowsDependency; + } + } + + // Generating cs/h files for app code consumption + if (projectLang === 'cs') { + let csUsingNamespaces = ''; + let csReactPacakgeProviders = ''; + + for (const dependencyName in windowsDependencies) { + windowsDependencies[dependencyName].projects.forEach(project => { + csUsingNamespaces += `\n\n// Namespaces from ${dependencyName}`; + project.csNamespaces.forEach(namespace => { + csUsingNamespaces += `\nusing ${namespace};`; + }); + + csReactPacakgeProviders += `\n // IReactPackageProviders from ${dependencyName}`; + project.csPackageProviders.forEach(packageProvider => { + csReactPacakgeProviders += `\n packageProviders.Add(new ${packageProvider}());`; + }); + }); + } + + const csFile = path.join(projectDir, 'AutolinkedNativeModules.g.cs'); + + verboseMessage( + `Calculating ${chalk.bold(path.basename(csFile))}...`, + verbose, ); - return; + + const csContents = `// AutolinkedNativeModules.g.cs contents generated by "react-native autolink-windows" + +using System.Collections.Generic;${csUsingNamespaces} + +namespace Microsoft.ReactNative.Managed +{ + internal static class AutolinkedNativeModules + { + internal static void RegisterAutolinkedNativeModulePackages(IList packageProviders) + {${csReactPacakgeProviders} + } } +} +`; + + changesNecessary = + changesNecessary || + updateFile(csFile, '\uFEFF' + csContents, verbose, checkMode); + } else if (projectLang === 'cpp') { + let cppIncludes = ''; + let cppPackageProviders = ''; + + for (const dependencyName in windowsDependencies) { + windowsDependencies[dependencyName].projects.forEach(project => { + cppIncludes += `\n\n// Includes from ${dependencyName}`; + project.cppHeaders.forEach(header => { + cppIncludes += `\n#include <${header}>`; + }); - //TODO: Update to the new RegisterAutolinkedNativeModulePackages method - - let generatedIncludes = - '// AutolinkedNativeModules.g.h -- contents generated by "react-native run-windows"\\\r\n\\\r\n#pragma once\\\r\n\\\r\n'; - const dependencies = config.dependencies; - let packageRegistrations = - '#define REGISTER_AUTOLINKED_NATIVE_MODULE_PACKAGES()'; - for (const dependency in dependencies) { - const windowDependency = dependencies[dependency].platforms.windows; - const cppProjFile = windowDependency - ? windowDependency.cppProjFile - : null; - if (cppProjFile == null) { - console.log('No C++/WinRT project found for ' + dependency); - continue; + cppPackageProviders += `\n // IReactPackageProviders from ${dependencyName}`; + project.cppPackageProviders.forEach(packageProvider => { + cppPackageProviders += `\n packageProviders.Append(winrt::${packageProvider}());`; + }); + }); } - console.log( - `Adding include and package provider statement for ${dependency}`, + + const cppHeaderFile = path.join( + projectDir, + 'AutolinkedNativeModules.g.h', + ); + + verboseMessage( + `Calculating ${chalk.bold(path.basename(cppHeaderFile))}...`, + verbose, ); - const trimmedPackageName = windowDependency.packageName.trim(); - const includeStatement = `\r\n#include `; - generatedIncludes += includeStatement; - const packageRegistration = ` \\\r\n PackageProviders().Append(winrt::${trimmedPackageName}::ReactPackageProvider());`; - packageRegistrations += packageRegistration; + + const cppHeaderContents = `// AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" + +#include "pch.h"${cppIncludes} + +#pragma once + +namespace winrt::Microsoft::ReactNative +{ + +static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +{${cppPackageProviders} +} + +} +`; + + changesNecessary = + updateFile(cppHeaderFile, cppHeaderContents, verbose, checkMode) || + changesNecessary; } - console.log('Updating AutolinkedNativeModules.g.h...'); - const contents = generatedIncludes + '\r\n' + packageRegistrations; - fs.writeFileSync(generatedHeader, contents, {encoding: 'utf8', flag: 'w'}); + // Generating targets for app project consumption + let projectReferencesForTargets = ''; + + for (const dependencyName in windowsDependencies) { + windowsDependencies[dependencyName].projects.forEach(project => { + const dependencyProjectFile = path.join( + windowsDependencies[dependencyName].folder, + windowsDependencies[dependencyName].sourceDir, + project.projectFile, + ); - //TODO: - //#2. Update project file to add references to native module packages + const relDependencyProjectFile = path.relative( + projectDir, + dependencyProjectFile, + ); - //TODO: - //#3. Update solution file to include native module project files + projectReferencesForTargets += `\n `; + projectReferencesForTargets += `\n + ${project.projectGuid} + `; + }); + } + + const targetFile = path.join( + projectDir, + 'AutolinkedNativeModules.g.targets', + ); - return; + verboseMessage( + `Calculating ${chalk.bold(path.basename(targetFile))}...`, + verbose, + ); + + const targetContents = ` + + + ${projectReferencesForTargets} + + +`; + + changesNecessary = + updateFile(targetFile, targetContents, verbose, checkMode) || + changesNecessary; + + // Generating project entries for solution + let projectsForSolution = []; + + for (const dependencyName in windowsDependencies) { + // Process projects + windowsDependencies[dependencyName].projects.forEach(project => { + const dependencyProjectFile = path.join( + windowsDependencies[dependencyName].folder, + windowsDependencies[dependencyName].sourceDir, + project.projectFile, + ); + + projectsForSolution.push({ + projectFile: dependencyProjectFile, + projectName: project.projectName, + projectLang: project.projectLang, + projectGuid: project.projectGuid, + }); + }); + + // Process additional projects + windowsDependencies[dependencyName].additionalProjects.forEach( + project => { + const dependencyProjectFile = path.join( + windowsDependencies[dependencyName].folder, + windowsDependencies[dependencyName].sourceDir, + project.projectFile, + ); + + projectsForSolution.push({ + projectFile: dependencyProjectFile, + projectName: project.projectName, + projectLang: project.projectLang, + projectGuid: project.projectGuid, + }); + }, + ); + } + + verboseMessage( + `Calculating ${chalk.bold(path.basename(solutionFile))} changes...`, + verbose, + ); + + projectsForSolution.forEach(project => { + const contentsChanged = vstools.addProjectToSolution( + solutionFile, + project, + verbose, + checkMode, + ); + changesNecessary = changesNecessary || contentsChanged; + }); + + spinner.succeed(); + + if (!changesNecessary) { + console.log( + `${chalk.green('Success:')} No auto-linking changes necessary.`, + ); + } else if (checkMode) { + console.log( + `${chalk.yellow( + 'Warning:', + )} Auto-linking changes were necessary but ${chalk.bold( + '--check', + )} specified. Run ${chalk.bold( + "'npx react-native autolink-windows'", + )} to apply the changes.`, + ); + ExitProcessWithStatusCode(0, verbose); + } else { + console.log(`${chalk.green('Success:')} Auto-linking changes completed.`); + } } catch (e) { - console.error('Parsing react-native config failed!'); - console.error(e); - return; + spinner.fail(); + console.log(chalk.red('Error:') + ' ' + e.toString()); + ExitProcessWithStatusCode(1, verbose); } + + return; } module.exports = { - updateAutoLink, + name: 'autolink-windows', + description: 'performs autolinking', + func: updateAutoLink, + options: [ + { + command: '--logging', + description: 'Verbose output logging', + default: false, + }, + { + command: '--check', + description: 'Check (only) if any autolinked files need to change', + default: false, + }, + ], }; diff --git a/vnext/local-cli/runWindows/utils/vstools.js b/vnext/local-cli/runWindows/utils/vstools.js new file mode 100644 index 00000000000..a7cf1e69894 --- /dev/null +++ b/vnext/local-cli/runWindows/utils/vstools.js @@ -0,0 +1,176 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. + * @format + */ +// @ts-check + +const fs = require('fs'); +const path = require('path'); +const chalk = require('chalk'); + +function hasBlock(lines, block) { + var startIndex = lines.indexOf(block[0]); + + if (startIndex >= 0) { + for (let i = 1; i < block.length; i++) { + if (lines[startIndex + i] !== block[i]) { + return false; + } + } + return true; + } + + return false; +} + +function insertBlock(lines, block, index) { + for (let i = 0; i < block.length; i++) { + lines.splice(index + i, 0, block[i]); + } +} + +function getBlockContents(lines, startLine, endLine, includeStartEnd = true) { + const startIndex = lines.indexOf(startLine); + const endIndex = lines.indexOf(endLine, startIndex); + + if (startIndex >= 0 && startIndex < endIndex) { + if (includeStartEnd) { + return lines.slice(startIndex, endIndex + 1); + } else if (startIndex + 1 < endIndex) { + return lines.slice(startIndex + 1, endIndex); + } + } + + return []; +} + +function addProjectToSolution( + slnFile, + project, + verbose = false, + checkMode = false, +) { + if (verbose) { + console.log( + `Processing ${chalk.bold(path.basename(project.projectFile))}...`, + ); + } + + let slnLines = fs + .readFileSync(slnFile) + .toString() + .split('\r\n'); + + let contentsChanged = false; + + // Check for the project entry block + + const slnDir = path.dirname(slnFile); + const relProjectFile = path.relative(slnDir, project.projectFile); + + const projectTypeGuid = + project.projectLang === 'cpp' + ? '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' + : '{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}'; + + const projectGuid = project.projectGuid.toUpperCase(); + + const projectEntryBlock = [ + `Project("${projectTypeGuid}") = "${ + project.projectName + }", "${relProjectFile}", "${projectGuid}"`, + 'EndProject', + ]; + + if (!hasBlock(slnLines, projectEntryBlock)) { + if (verbose) { + console.log(chalk.yellow('Missing project entry block.')); + } + + const globalIndex = slnLines.indexOf('Global'); + insertBlock(slnLines, projectEntryBlock, globalIndex); + contentsChanged = true; + } + + // Check for the project configuration platforms + + const slnConfigs = getBlockContents( + slnLines, + '\tGlobalSection(SolutionConfigurationPlatforms) = preSolution', + '\tEndGlobalSection', + false, + ).map(line => line.match(/\s+([\w|]+)\s=/)[1]); + + let projectConfigLines = []; + + slnConfigs.forEach(slnConfig => { + projectConfigLines.push( + `\t\t${projectGuid}.${slnConfig}.ActiveCfg = ${slnConfig.replace( + 'x86', + 'Win32', + )}`, + ); + projectConfigLines.push( + `\t\t${projectGuid}.${slnConfig}.Build.0 = ${slnConfig.replace( + 'x86', + 'Win32', + )}`, + ); + }); + + const projectConfigStartIndex = slnLines.indexOf( + '\tGlobalSection(ProjectConfigurationPlatforms) = postSolution', + ); + + projectConfigLines.forEach(projectConfigLine => { + if (slnLines.indexOf(projectConfigLine) < 0) { + if (verbose) { + console.log(chalk.yellow('Missing project config block.')); + } + + const projectConfigEndIndex = slnLines.indexOf( + '\tEndGlobalSection', + projectConfigStartIndex, + ); + + slnLines.splice(projectConfigEndIndex, 0, projectConfigLine); + contentsChanged = true; + } + }); + + // Write out new solution file if there were changes + if (contentsChanged) { + if (verbose) { + console.log( + chalk.yellow( + `${chalk.bold(path.basename(slnFile))} needs to be updated.`, + ), + ); + } + + if (!checkMode) { + if (verbose) { + console.log( + `Writing changes to ${chalk.bold(path.basename(slnFile))}...`, + ); + } + + const slnContents = slnLines.join('\r\n'); + fs.writeFileSync(slnFile, slnContents, { + encoding: 'utf8', + flag: 'w', + }); + } + } else { + if (verbose) { + console.log(`No changes to ${chalk.bold(path.basename(slnFile))}.`); + } + } + + return contentsChanged; +} + +module.exports = { + addProjectToSolution: addProjectToSolution, +}; diff --git a/vnext/react-native.config.js b/vnext/react-native.config.js index 9a81411678b..0dcc82c393d 100644 --- a/vnext/react-native.config.js +++ b/vnext/react-native.config.js @@ -6,6 +6,7 @@ module.exports = { // **** This section defined commands and options on how to provide the windows platform to external applications commands: [ require('./local-cli/runWindows/runWindows'), + require('./local-cli/runWindows/utils/autolink'), ], platforms: { windows: { From 0efda8d4dd3444053f9f37ccbdf67a8ed81b87c6 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 28 May 2020 13:42:40 -0700 Subject: [PATCH 02/28] Change files --- ...react-native-windows-2020-05-28-13-42-40-autolink.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 change/react-native-windows-2020-05-28-13-42-40-autolink.json diff --git a/change/react-native-windows-2020-05-28-13-42-40-autolink.json b/change/react-native-windows-2020-05-28-13-42-40-autolink.json new file mode 100644 index 00000000000..756f22dda3b --- /dev/null +++ b/change/react-native-windows-2020-05-28-13-42-40-autolink.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Auto-linking for native modules (source-based) * Updated `npx react-native config` to create the needed json for auto-linking * New `npx react-native autolink-windows` command to perform auto-linking * Auto-linking occurs automatically during `npx react-native run-windows` * New Auto-linking targets and props so CLI apps throw a warning if auto-linking should be run * New targets and props for native modules to consume", + "packageName": "react-native-windows", + "email": "jthysell@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-05-28T20:42:40.417Z" +} From e3ddf46c27da5669c88a62d1eae45a5db9d978a1 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 28 May 2020 15:53:35 -0700 Subject: [PATCH 03/28] Addressing feedback: fix check description --- vnext/local-cli/runWindows/utils/autolink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index 1cd22879442..ee3ca1f9953 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -357,7 +357,7 @@ module.exports = { }, { command: '--check', - description: 'Check (only) if any autolinked files need to change', + description: 'Only check whether any autolinked files need to change', default: false, }, ], From 4b24f68bfaab625e21d9fc1f7506309bf1255010 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 28 May 2020 16:32:11 -0700 Subject: [PATCH 04/28] Addressing feedback: remove ConsoleToMsBuild --- vnext/PropertySheets/Autolink.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnext/PropertySheets/Autolink.targets b/vnext/PropertySheets/Autolink.targets index 94f88bba2f9..924c3f6d5a8 100644 --- a/vnext/PropertySheets/Autolink.targets +++ b/vnext/PropertySheets/Autolink.targets @@ -5,6 +5,6 @@ --> - + From d4602d067e994cc0f3540bb20f5e1b216ad15b5d Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Fri, 29 May 2020 09:43:47 -0700 Subject: [PATCH 05/28] Addressing feedback: fix changelog entry --- change/react-native-windows-2020-05-28-13-42-40-autolink.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change/react-native-windows-2020-05-28-13-42-40-autolink.json b/change/react-native-windows-2020-05-28-13-42-40-autolink.json index 756f22dda3b..e449a4b55dc 100644 --- a/change/react-native-windows-2020-05-28-13-42-40-autolink.json +++ b/change/react-native-windows-2020-05-28-13-42-40-autolink.json @@ -1,6 +1,6 @@ { "type": "prerelease", - "comment": "Auto-linking for native modules (source-based) * Updated `npx react-native config` to create the needed json for auto-linking * New `npx react-native autolink-windows` command to perform auto-linking * Auto-linking occurs automatically during `npx react-native run-windows` * New Auto-linking targets and props so CLI apps throw a warning if auto-linking should be run * New targets and props for native modules to consume", + "comment": "Auto-linking for native modules (source-based)", "packageName": "react-native-windows", "email": "jthysell@microsoft.com", "dependentChangeType": "patch", From 9209a737b5b9590f9901d253eaf1f88d895392da Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Fri, 29 May 2020 09:48:02 -0700 Subject: [PATCH 06/28] Addressing feedback: autolink nit --- vnext/local-cli/runWindows/utils/autolink.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index ee3ca1f9953..862bc03cb1c 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -47,7 +47,7 @@ function updateFile(filePath, expectedContents, verbose, checkMode) { return contentsChanged; } -function ExitProcessWithStatusCode(statusCode, loggingWasEnabled) { +function exitProcessWithStatusCode(statusCode, loggingWasEnabled) { if (!loggingWasEnabled && statusCode !== 0) { console.log( `Error: Re-run the command with ${chalk.bold( @@ -332,17 +332,15 @@ static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::C "'npx react-native autolink-windows'", )} to apply the changes.`, ); - ExitProcessWithStatusCode(0, verbose); + exitProcessWithStatusCode(0, verbose); } else { console.log(`${chalk.green('Success:')} Auto-linking changes completed.`); } } catch (e) { spinner.fail(); console.log(chalk.red('Error:') + ' ' + e.toString()); - ExitProcessWithStatusCode(1, verbose); + exitProcessWithStatusCode(1, verbose); } - - return; } module.exports = { From bc5df118a13f272a5c2c19796ae25073a3c8325b Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Fri, 29 May 2020 09:57:25 -0700 Subject: [PATCH 07/28] Addressing feedback: project type guids const --- vnext/local-cli/runWindows/utils/vstools.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/vnext/local-cli/runWindows/utils/vstools.js b/vnext/local-cli/runWindows/utils/vstools.js index a7cf1e69894..a89baa5f907 100644 --- a/vnext/local-cli/runWindows/utils/vstools.js +++ b/vnext/local-cli/runWindows/utils/vstools.js @@ -9,6 +9,11 @@ const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); +const projectTypeGuidsByLanguage = { + cpp: '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}', + cs: '{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}', +}; + function hasBlock(lines, block) { var startIndex = lines.indexOf(block[0]); @@ -69,10 +74,7 @@ function addProjectToSolution( const slnDir = path.dirname(slnFile); const relProjectFile = path.relative(slnDir, project.projectFile); - const projectTypeGuid = - project.projectLang === 'cpp' - ? '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' - : '{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}'; + const projectTypeGuid = projectTypeGuidsByLanguage[project.projectLang]; const projectGuid = project.projectGuid.toUpperCase(); From c5c2d5731b40316632761966001f364ee7cf7c7e Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Fri, 29 May 2020 10:58:06 -0700 Subject: [PATCH 08/28] Addressing feedback: name args when runWindows calls autolinkWindows --- vnext/local-cli/runWindows/runWindows.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index 5b47abe4779..2fff6d2d437 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -52,7 +52,10 @@ async function runWindows(config, args, options) { const slnFile = options.sln || build.getSolutionFile(options); if (options.autolink) { - await autolink.func(null, null, {logging: options.logging}); + const autolinkConfig = config; + const autolinkArgs = []; + const autoLinkOptions = {logging: options.logging}; + await autolink.func(autolinkConfig, autolinkArgs, autoLinkOptions); } if (options.build) { From 62cfeaa2859f4b99da6bca6d6a8b9c420df3f9d3 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Mon, 1 Jun 2020 12:15:36 -0700 Subject: [PATCH 09/28] Addressing feedback: simplified config schema --- vnext/local-cli/config/dependencyConfig.js | 182 ++++++++++++------- vnext/local-cli/config/projectConfig.js | 74 +++++--- vnext/local-cli/runWindows/utils/autolink.js | 86 ++++----- 3 files changed, 199 insertions(+), 143 deletions(-) diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 771f4b0cfcb..f488726da1f 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -11,40 +11,43 @@ const configUtils = require('./configUtils.js'); /* +react-native config will generate the following JSON for each native module dependency +under node_modules that has a windows implementation, in order to support auto-linking. +This is done heurestically, so if the result isn't quite correct, native module developers +can provide a manual override file: react-native.config. + Schema for dependencies: +Tags: +auto - Item is always calculated by config. An override file should NEVER provide it. +req - Item is required. If an override file exists, it MUST provide it. If no override file exists, config will try to calculate it. +opt - Item is optional. If an override file exists, it MAY provide it. If no override file exists, config may try to calculate it. + { - folder: '', // absolute path to the module root, ex: c:\path\to\node_modules\module-name - sourceDir: '', // relative path to the windows implementation folder, ex: windows - solutionFile: '', // relative path to the solution, ex: ModuleName.sln - projects: [ // array of projects to be built that include modules - { - projectFile: '', // relative path to the project, ex: ProjectName\ProjectName.vcxproj - projectName: '', // name of the project - projectLang: '', // language of the project, cpp or cs - projectGuid: '', // project identifier - cppHeaders: [], // array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' - cppPackageProviders: [], // array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' - csNamespaces: [], // array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' - csPackageProviders: [], // array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' - }, - ], - additionalProjects: [ // array of (dependency) projects that must be built, but do not contain modules + folder: string, // (auto) Absolute path to the module root folder, determined by react-native config, ex: 'c:\path\to\app-name\node_modules\module-name' + sourceDir: string, // (req) Relative path to the windows implementation under folder, ex: 'windows' + solutionFile: string, // (req) Relative path to the module's VS solution file under sourceDir, ex: 'ModuleName.sln' + projects: [ // (opt) Array of VS projects that must be added to the consuming app's solution file, so they are built { - projectFile: '', // relative path to the project, ex: ProjectName\ProjectName.vcxproj - projectName: '', // name of the project - projectLang: '', // language of the project, cpp or cs - projectGuid: '', // project identifier + projectFile: string, // (req) Relative path to the VS project file under sourceDir, ex: 'ProjectName\ProjectName.vcxproj' for 'c:\path\to\app-name\node_modules\module-name\windows\ProjectName\ProjectName.vcxproj' + projectName: string, // (auto) Name of the project, determined from projectFile, ex: 'ProjectName' + projectLang: string, // (auto) Language of the project, cpp or cs, determined from projectFile + projectGuid: string, // (auto) Project identifier, determined from projectFile + directDependency: bool, // (req) Whether to add the project file as a dependency to the consuming app's project file. true for projects that provide native modules + cppHeaders: [], // (opt, but req if directDependency) Array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' + cppPackageProviders: [], // (opt, but req if directDependency) Array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' + csNamespaces: [], // (opt, but req if directDependency) Array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' + csPackageProviders: [], // (opt, but req if directDependency) Array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' }, ], - packages: [ // array of nuget packages that include modules + nugetPackages: [ // (opt) Array of nuget packages including native modules that must be added as a dependency to the consuming app. It can be empty, but by its nature it can't be calculated { - packageName: '', // name of the nuget package to install - packageVersion: '', // version of the nuget package to install - cppHeaders: [], // array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' - cppPackageProviders: [], // array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' - csNamespaces: [], // array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' - csPackageProviders: [], // array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' + packageName: string, // (req) Name of the nuget package to install + packageVersion: string, // (req) Version of the nuget package to install + cppHeaders: [], // (req) Array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' + cppPackageProviders: [], // (req) Array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' + csNamespaces: [], // (req) Array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' + csPackageProviders: [], // (req) Array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' }, ], } @@ -52,65 +55,112 @@ Schema for dependencies: */ function dependencyConfigWindows(folder, userConfig = {}) { - if (userConfig.sourceDir) { - return { - folder, - sourceDir: userConfig.sourceDir, - solutionFile: userConfig.solutionFile, - projects: userConfig.projects || [], - additionalProjects: userConfig.additionalProjects || [], - packages: userConfig.packages || [], - }; - } + const usingManualOverride = 'sourceDir' in userConfig; - const sourceDir = configUtils.findWindowsFolder(folder); + const sourceDir = usingManualOverride + ? path.join(folder, userConfig.sourceDir) + : configUtils.findWindowsFolder(folder); - if (!sourceDir) { + if (sourceDir === null) { return null; } + if (usingManualOverride && !('solutionFile' in userConfig)) { + throw 'solutionFile is required but not specified in react-native.config'; + } + const packageJson = require(path.join(folder, 'package.json')); - const solutionFile = configUtils.findSolutionFile(sourceDir, packageJson); + const solutionFile = usingManualOverride + ? userConfig.solutionFile + : configUtils.findSolutionFile(sourceDir, packageJson); + + var projects = usingManualOverride ? userConfig.projects || [] : []; + var nugetPackages = usingManualOverride ? userConfig.nugetPackages || [] : []; + + if (usingManualOverride) { + // react-native.config used, fill out (auto) items for each provided project, verify (req) items are present + + const alwaysRequired = ['projectFile', 'directDependency']; - const projectFile = configUtils.findProjectFile(sourceDir, packageJson); + const requiredForDirectDepenencies = [ + 'cppHeaders', + 'cppPackageProviders', + 'csNamespaces', + 'csPackageProviders', + ]; - const projectLang = configUtils.getProjectLanguage(projectFile); + for (let i = 0; i < projects.length; i++) { + // Verifying (req) items + alwaysRequired.forEach(item => { + if (!(item in projects[i])) { + throw `${item} is required for each project in react-native.config`; + } + }); - const projectXml = configUtils.readProjectFile(projectFile); + const projectFile = path.join(sourceDir, projects[i].projectFile); - const projectName = configUtils.getProjectName(projectXml); + const projectXml = configUtils.readProjectFile(projectFile); - const projectGuid = configUtils.getProjectGuid(projectXml); + // Calculating (auto) items - const projectNamespace = configUtils.getProjectNamespace(projectXml); + projects[i].projectName = configUtils.getProjectName(projectXml); + projects[i].projectLang = configUtils.getProjectLanguage(projectFile); + projects[i].projectGuid = configUtils.getProjectGuid(projectXml); - const cppNamespace = projectNamespace.replace('.', '::'); - const csNamespace = projectNamespace.replace('::', '.'); + if (projects[i].directDependency) { + // Verifying (req) items + requiredForDirectDepenencies.forEach(item => { + if (!(item in projects[i])) { + throw `${item} is required for each project in react-native.config`; + } + }); + } + } + } else { + // No react-native.config, try to heurestically find the correct project - var cppHeaders = [`winrt/${csNamespace}.h`]; - var cppPackageProviders = [`${cppNamespace}::ReactPackageProvider`]; - var csNamespaces = [`${csNamespace}`]; - var csPackageProviders = [`${csNamespace}.ReactPackageProvider`]; + const projectFile = configUtils.findProjectFile(sourceDir, packageJson); + + const projectLang = configUtils.getProjectLanguage(projectFile); + + const projectXml = configUtils.readProjectFile(projectFile); + + const projectName = configUtils.getProjectName(projectXml); + + const projectGuid = configUtils.getProjectGuid(projectXml); + + const projectNamespace = configUtils.getProjectNamespace(projectXml); + + const directDependency = true; + + const cppNamespace = projectNamespace.replace('.', '::'); + const csNamespace = projectNamespace.replace('::', '.'); + + const cppHeaders = [`winrt/${csNamespace}.h`]; + const cppPackageProviders = [`${cppNamespace}::ReactPackageProvider`]; + const csNamespaces = [`${csNamespace}`]; + const csPackageProviders = [`${csNamespace}.ReactPackageProvider`]; + + projects.push({ + projectFile: projectFile.substr(sourceDir.length + 1), + projectName, + projectLang, + projectGuid, + directDependency, + cppHeaders, + cppPackageProviders, + csNamespaces, + csPackageProviders, + }); + } return { folder, sourceDir: sourceDir.substr(folder.length + 1), solutionFile: solutionFile.substr(sourceDir.length + 1), - projects: [ - { - projectFile: projectFile.substr(sourceDir.length + 1), - projectName, - projectLang, - projectGuid, - cppHeaders, - cppPackageProviders, - csNamespaces, - csPackageProviders, - }, - ], - additionalProjects: [], - packages: [], + projects, + nugetPackages, }; } diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index d4d24b5ff40..2e8dbb5db86 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -11,51 +11,69 @@ const configUtils = require('./configUtils.js'); /* +react-native config will generate the following JSON for app projects that have a +windows implementation, as a target for auto-linking. This is done heurestically, +so if the result isn't quite correct, app developers can provide a manual override +file: react-native.config. + Schema for app projects: +Tags: +auto - Item is always calculated by config. An override file should NEVER provide it. +req - Item is required. If an override file exists, it MUST provide it. If no override file exists, config will try to calculate it. +opt - Item is optional. If an override file exists, it MAY provide it. If no override file exists, config may try to calculate it. + { - folder: '', // absolute path to the project root, ex: c:\path\project - sourceDir: '', // relative path to the windows implementation folder, ex: windows - solutionFile: '', // relative path to the solution, ex: ProjectName.sln - project: { - projectFile: '', // relative path to the project, ex: ProjectName\ProjectName.vcxproj - projectName: '', // name of the project - projectLang: '', // language of the project, cpp or cs - projectGuid: '', // project identifier + folder: string, // (auto) Absolute path to the app root folder, determined by react-native config, ex: 'c:\path\to\app-name' + sourceDir: string, // (req) Relative path to the windows implementation under folder, ex: 'windows' + solutionFile: string, // (req) Relative path to the app's VS solution file under sourceDir, ex: 'AppName.sln' + project: { // (req) + projectFile: string, // (req) Relative path to the VS project file under sourceDir, ex: 'AppName\AppName.vcxproj' for 'c:\path\to\app-name\windows\AppName\AppName.vcxproj' + projectName: string, // (auto) Name of the project, determined from projectFile, ex: 'AppName' + projectLang: string, // (auto) Language of the project, cpp or cs, determined from projectFile + projectGuid: string, // (auto) Project identifier, determined from projectFile }, } */ function projectConfigWindows(folder, userConfig = {}) { - if (userConfig.sourceDir) { - return { - folder, - sourceDir: userConfig.sourceDir, - solutionFile: userConfig.solutionFile, - project: userConfig.project, - }; - } + const usingManualOverride = 'sourceDir' in userConfig; - const sourceDir = configUtils.findWindowsFolder(folder); + const sourceDir = usingManualOverride + ? path.join(folder, userConfig.sourceDir) + : configUtils.findWindowsFolder(folder); - if (!sourceDir) { + if (sourceDir === null) { return null; } - const packageJson = require(path.join(folder, 'package.json')); + const alwaysRequired = ['solutionFile', 'project']; - const solutionFile = configUtils.findSolutionFile(sourceDir, packageJson); + if (usingManualOverride) { + // Verifying (req) items + alwaysRequired.forEach(item => { + if (!(item in userConfig)) { + throw `${item} is required but not specified in react-native.config`; + } + }); + } - const projectFile = configUtils.findProjectFile(sourceDir, packageJson); + const packageJson = require(path.join(folder, 'package.json')); - const projectLang = configUtils.getProjectLanguage(projectFile); + const solutionFile = usingManualOverride + ? userConfig.solutionFile + : configUtils.findSolutionFile(sourceDir, packageJson); - const projectXml = configUtils.readProjectFile(projectFile); + if (usingManualOverride && !('projectFile' in userConfig.project)) { + throw 'projectFile is required but not specified in react-native.config'; + } - const projectName = configUtils.getProjectName(projectXml); + const projectFile = usingManualOverride + ? path.join(sourceDir, userConfig.project.projectFile) + : configUtils.findProjectFile(sourceDir, packageJson); - const projectGuid = configUtils.getProjectGuid(projectXml); + const projectXml = configUtils.readProjectFile(projectFile); return { folder, @@ -63,9 +81,9 @@ function projectConfigWindows(folder, userConfig = {}) { solutionFile: solutionFile.substr(sourceDir.length + 1), project: { projectFile: projectFile.substr(sourceDir.length + 1), - projectName, - projectLang, - projectGuid, + projectName: configUtils.getProjectName(projectXml), + projectLang: configUtils.getProjectLanguage(projectFile), + projectGuid: configUtils.getProjectGuid(projectXml), }, }; } diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index 862bc03cb1c..05d65bb6531 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -131,15 +131,17 @@ async function updateAutoLink(config, args, options) { for (const dependencyName in windowsDependencies) { windowsDependencies[dependencyName].projects.forEach(project => { - csUsingNamespaces += `\n\n// Namespaces from ${dependencyName}`; - project.csNamespaces.forEach(namespace => { - csUsingNamespaces += `\nusing ${namespace};`; - }); - - csReactPacakgeProviders += `\n // IReactPackageProviders from ${dependencyName}`; - project.csPackageProviders.forEach(packageProvider => { - csReactPacakgeProviders += `\n packageProviders.Add(new ${packageProvider}());`; - }); + if (project.directDependency) { + csUsingNamespaces += `\n\n// Namespaces from ${dependencyName}`; + project.csNamespaces.forEach(namespace => { + csUsingNamespaces += `\nusing ${namespace};`; + }); + + csReactPacakgeProviders += `\n // IReactPackageProviders from ${dependencyName}`; + project.csPackageProviders.forEach(packageProvider => { + csReactPacakgeProviders += `\n packageProviders.Add(new ${packageProvider}());`; + }); + } }); } @@ -166,23 +168,25 @@ namespace Microsoft.ReactNative.Managed `; changesNecessary = - changesNecessary || - updateFile(csFile, '\uFEFF' + csContents, verbose, checkMode); + updateFile(csFile, '\uFEFF' + csContents, verbose, checkMode) || + changesNecessary; } else if (projectLang === 'cpp') { let cppIncludes = ''; let cppPackageProviders = ''; for (const dependencyName in windowsDependencies) { windowsDependencies[dependencyName].projects.forEach(project => { - cppIncludes += `\n\n// Includes from ${dependencyName}`; - project.cppHeaders.forEach(header => { - cppIncludes += `\n#include <${header}>`; - }); - - cppPackageProviders += `\n // IReactPackageProviders from ${dependencyName}`; - project.cppPackageProviders.forEach(packageProvider => { - cppPackageProviders += `\n packageProviders.Append(winrt::${packageProvider}());`; - }); + if (project.directDependency) { + cppIncludes += `\n\n// Includes from ${dependencyName}`; + project.cppHeaders.forEach(header => { + cppIncludes += `\n#include <${header}>`; + }); + + cppPackageProviders += `\n // IReactPackageProviders from ${dependencyName}`; + project.cppPackageProviders.forEach(packageProvider => { + cppPackageProviders += `\n packageProviders.Append(winrt::${packageProvider}());`; + }); + } }); } @@ -222,21 +226,23 @@ static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::C for (const dependencyName in windowsDependencies) { windowsDependencies[dependencyName].projects.forEach(project => { - const dependencyProjectFile = path.join( - windowsDependencies[dependencyName].folder, - windowsDependencies[dependencyName].sourceDir, - project.projectFile, - ); + if (project.directDependency) { + const dependencyProjectFile = path.join( + windowsDependencies[dependencyName].folder, + windowsDependencies[dependencyName].sourceDir, + project.projectFile, + ); - const relDependencyProjectFile = path.relative( - projectDir, - dependencyProjectFile, - ); + const relDependencyProjectFile = path.relative( + projectDir, + dependencyProjectFile, + ); - projectReferencesForTargets += `\n `; - projectReferencesForTargets += `\n + projectReferencesForTargets += `\n `; + projectReferencesForTargets += `\n ${project.projectGuid} `; + } }); } @@ -281,24 +287,6 @@ static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::C projectGuid: project.projectGuid, }); }); - - // Process additional projects - windowsDependencies[dependencyName].additionalProjects.forEach( - project => { - const dependencyProjectFile = path.join( - windowsDependencies[dependencyName].folder, - windowsDependencies[dependencyName].sourceDir, - project.projectFile, - ); - - projectsForSolution.push({ - projectFile: dependencyProjectFile, - projectName: project.projectName, - projectLang: project.projectLang, - projectGuid: project.projectGuid, - }); - }, - ); } verboseMessage( From 72a046edd2d885d459e1bb095ac7153c6d72e089 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Tue, 2 Jun 2020 11:20:25 -0700 Subject: [PATCH 10/28] Addressing feedback: fixing RegisterAutolinkedNativeModulePackages --- .../windows/SampleAppCPP/App.cpp | 3 +++ .../AutolinkedNativeModules.g.cpp | 13 ++++++++++++ .../SampleAppCPP/AutolinkedNativeModules.g.h | 8 ++------ .../windows/SampleAppCPP/SampleAppCpp.vcxproj | 2 ++ .../SampleAppCPP/SampleAppCpp.vcxproj.filters | 2 ++ .../windows/SampleAppCPP/pch.h | 2 -- packages/playground/react-native.config.js | 18 ++++++++--------- .../playground/windows/playground-win32.sln | 3 +-- .../AutolinkedNativeModules.g.cpp | 13 ++++++++++++ .../AutolinkedNativeModules.g.h | 12 ++++------- .../playground-win32/Playground-win32.vcxproj | 2 +- .../playground/windows/playground-win32/pch.h | 2 -- .../playground/AutolinkedNativeModules.g.cpp | 13 ++++++++++++ .../playground/AutolinkedNativeModules.g.h | 8 ++------ .../windows/playground/Playground.vcxproj | 2 ++ .../playground/Playground.vcxproj.filters | 2 ++ packages/playground/windows/playground/pch.h | 2 -- .../cpp/proj-experimental/MyApp.vcxproj | 2 ++ .../proj-experimental/MyApp.vcxproj.filters | 2 ++ .../templates/cpp/proj/MyApp.vcxproj | 2 ++ .../templates/cpp/proj/MyApp.vcxproj.filters | 2 ++ .../templates/cpp/src/App.cpp | 2 ++ .../cpp/src/AutolinkedNativeModules.g.cpp | 13 ++++++++++++ .../cpp/src/AutolinkedNativeModules.g.h | 8 ++------ .../generator-windows/templates/cpp/src/pch.h | 2 -- vnext/local-cli/runWindows/utils/autolink.js | 20 ++++++++----------- 26 files changed, 102 insertions(+), 58 deletions(-) create mode 100644 packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp create mode 100644 packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp create mode 100644 packages/playground/windows/playground/AutolinkedNativeModules.g.cpp create mode 100644 vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/App.cpp b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/App.cpp index d2101aef7bd..02ff7604d29 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/App.cpp +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/App.cpp @@ -2,7 +2,10 @@ // Licensed under the MIT License. #include "pch.h" + #include "App.h" + +#include "AutolinkedNativeModules.g.h" #include #include #include diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp new file mode 100644 index 00000000000..7d268b12944 --- /dev/null +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp @@ -0,0 +1,13 @@ +// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +#include "pch.h" +#include "AutolinkedNativeModules.g.h" + +// clang-format off +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +{ +} + +} diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.h b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.h index 688fd2cd5f1..99c3efc78bd 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.h +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.h @@ -1,14 +1,10 @@ // AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" - -#include "pch.h" - #pragma once +// clang-format off namespace winrt::Microsoft::ReactNative { -static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ -} +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders); } diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj index 5b9711a4ae1..dcd216258ff 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj @@ -139,6 +139,7 @@ Code + App.xaml @@ -170,6 +171,7 @@ Code + Create diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj.filters b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj.filters index de9839ae11c..d29aa524fc1 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj.filters +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/SampleAppCpp.vcxproj.filters @@ -11,12 +11,14 @@ + + diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/pch.h b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/pch.h index c572aad46a6..1acd9356bbe 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/pch.h +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/pch.h @@ -30,5 +30,3 @@ #include #include #include - -#include "AutolinkedNativeModules.g.h" diff --git a/packages/playground/react-native.config.js b/packages/playground/react-native.config.js index 5aab1221101..5c1ea6bd594 100644 --- a/packages/playground/react-native.config.js +++ b/packages/playground/react-native.config.js @@ -1,12 +1,12 @@ module.exports = { // Uncomment the below when running autolink-windows for playground-win32 - project: { - windows: { - sourceDir: 'windows', - solutionFile: 'playground-win32.sln', - project: { - projectFile: 'playground-win32\\Playground-win32.vcxproj', - }, - }, - }, + // project: { + // windows: { + // sourceDir: 'windows', + // solutionFile: 'playground-win32.sln', + // project: { + // projectFile: 'playground-win32\\Playground-win32.vcxproj', + // }, + // }, + // }, }; diff --git a/packages/playground/windows/playground-win32.sln b/packages/playground/windows/playground-win32.sln index fbec06c55dd..17b51fe1d0b 100644 --- a/packages/playground/windows/playground-win32.sln +++ b/packages/playground/windows/playground-win32.sln @@ -26,7 +26,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\..\..\vnext\Co EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shared", "..\..\..\vnext\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\..\..\vnext\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\..\..\vnext\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}" EndProject @@ -35,7 +35,6 @@ Global ..\..\..\vnext\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9 ..\..\..\vnext\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9 ..\..\..\vnext\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9 - ..\..\..\vnext\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{8b88ffae-4dbc-49a2-afa5-d2477d4ad189}*SharedItemsImports = 4 ..\..\..\vnext\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4 ..\..\..\vnext\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9 ..\..\..\vnext\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9 diff --git a/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp new file mode 100644 index 00000000000..7d268b12944 --- /dev/null +++ b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp @@ -0,0 +1,13 @@ +// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +#include "pch.h" +#include "AutolinkedNativeModules.g.h" + +// clang-format off +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +{ +} + +} diff --git a/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.h b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.h index 688fd2cd5f1..6b826dd0b86 100644 --- a/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.h +++ b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.h @@ -1,14 +1,10 @@ // AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" - -#include "pch.h" - #pragma once -namespace winrt::Microsoft::ReactNative -{ +namespace winrt::Microsoft::ReactNative { -static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ -} +void RegisterAutolinkedNativeModulePackages( + winrt::Windows::Foundation::Collections::IVector const + &packageProviders); } diff --git a/packages/playground/windows/playground-win32/Playground-win32.vcxproj b/packages/playground/windows/playground-win32/Playground-win32.vcxproj index b79fb3f3e55..3eced54b17f 100644 --- a/packages/playground/windows/playground-win32/Playground-win32.vcxproj +++ b/packages/playground/windows/playground-win32/Playground-win32.vcxproj @@ -60,7 +60,7 @@ - + False diff --git a/packages/playground/windows/playground-win32/pch.h b/packages/playground/windows/playground-win32/pch.h index 6e8c1926355..cdf4a1731bc 100644 --- a/packages/playground/windows/playground-win32/pch.h +++ b/packages/playground/windows/playground-win32/pch.h @@ -16,6 +16,4 @@ #include -#include "AutolinkedNativeModules.g.h" - #pragma pop_macro("GetCurrentTime") diff --git a/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp b/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp new file mode 100644 index 00000000000..7d268b12944 --- /dev/null +++ b/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp @@ -0,0 +1,13 @@ +// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +#include "pch.h" +#include "AutolinkedNativeModules.g.h" + +// clang-format off +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +{ +} + +} diff --git a/packages/playground/windows/playground/AutolinkedNativeModules.g.h b/packages/playground/windows/playground/AutolinkedNativeModules.g.h index 688fd2cd5f1..99c3efc78bd 100644 --- a/packages/playground/windows/playground/AutolinkedNativeModules.g.h +++ b/packages/playground/windows/playground/AutolinkedNativeModules.g.h @@ -1,14 +1,10 @@ // AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" - -#include "pch.h" - #pragma once +// clang-format off namespace winrt::Microsoft::ReactNative { -static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ -} +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders); } diff --git a/packages/playground/windows/playground/Playground.vcxproj b/packages/playground/windows/playground/Playground.vcxproj index 6dcf23ea4f1..edbcec3594b 100644 --- a/packages/playground/windows/playground/Playground.vcxproj +++ b/packages/playground/windows/playground/Playground.vcxproj @@ -135,6 +135,7 @@ + App.xaml @@ -167,6 +168,7 @@ + Create diff --git a/packages/playground/windows/playground/Playground.vcxproj.filters b/packages/playground/windows/playground/Playground.vcxproj.filters index e0fa9658ed5..52c1ce6bbf9 100644 --- a/packages/playground/windows/playground/Playground.vcxproj.filters +++ b/packages/playground/windows/playground/Playground.vcxproj.filters @@ -11,11 +11,13 @@ + + diff --git a/packages/playground/windows/playground/pch.h b/packages/playground/windows/playground/pch.h index cb0024bd865..0cad8c87048 100644 --- a/packages/playground/windows/playground/pch.h +++ b/packages/playground/windows/playground/pch.h @@ -31,5 +31,3 @@ #include #include #include - -#include "AutolinkedNativeModules.g.h" diff --git a/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj b/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj index 71231229268..a4ed703519a 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj +++ b/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj @@ -111,6 +111,7 @@ Code + App.xaml @@ -141,6 +142,7 @@ Code + Create diff --git a/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj.filters b/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj.filters index 13a1efb1a61..86bc8d76b0c 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj.filters +++ b/vnext/local-cli/generator-windows/templates/cpp/proj-experimental/MyApp.vcxproj.filters @@ -11,11 +11,13 @@ + + diff --git a/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj b/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj index 27331dc99c1..55324af880a 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj +++ b/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj @@ -110,6 +110,7 @@ Code + App.xaml @@ -140,6 +141,7 @@ Code + Create diff --git a/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj.filters b/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj.filters index 1fa33b7299b..2aa08b712a1 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj.filters +++ b/vnext/local-cli/generator-windows/templates/cpp/proj/MyApp.vcxproj.filters @@ -11,11 +11,13 @@ + + diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/App.cpp b/vnext/local-cli/generator-windows/templates/cpp/src/App.cpp index 876e5c6f324..3a9359a0b77 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/src/App.cpp +++ b/vnext/local-cli/generator-windows/templates/cpp/src/App.cpp @@ -1,6 +1,8 @@ #include "pch.h" #include "App.h" + +#include "AutolinkedNativeModules.g.h" #include "ReactPackageProvider.h" // clang-format off diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp new file mode 100644 index 00000000000..7d268b12944 --- /dev/null +++ b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp @@ -0,0 +1,13 @@ +// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +#include "pch.h" +#include "AutolinkedNativeModules.g.h" + +// clang-format off +namespace winrt::Microsoft::ReactNative +{ + +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +{ +} + +} diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h index c5eff9d90b3..99c3efc78bd 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h +++ b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h @@ -1,14 +1,10 @@ // AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" -// clang-format off -#include "pch.h" - #pragma once +// clang-format off namespace winrt::Microsoft::ReactNative { -static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ -} +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders); } diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/pch.h b/vnext/local-cli/generator-windows/templates/cpp/src/pch.h index 60036393aa9..58acd2a240b 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/src/pch.h +++ b/vnext/local-cli/generator-windows/templates/cpp/src/pch.h @@ -24,5 +24,3 @@ #include #include #include - -#include "AutolinkedNativeModules.g.h" diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index 1438c061d55..11cab26b6c5 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -190,26 +190,22 @@ namespace Microsoft.ReactNative.Managed }); } - const cppHeaderFile = path.join( - projectDir, - 'AutolinkedNativeModules.g.h', - ); + const cppFile = path.join(projectDir, 'AutolinkedNativeModules.g.cpp'); verboseMessage( - `Calculating ${chalk.bold(path.basename(cppHeaderFile))}...`, + `Calculating ${chalk.bold(path.basename(cppFile))}...`, verbose, ); - const cppHeaderContents = `// AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" - -#include "pch.h"${cppIncludes} - -#pragma once + const cppContents = `// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +#include "pch.h" +#include "AutolinkedNativeModules.g.h"${cppIncludes} +// clang-format off namespace winrt::Microsoft::ReactNative { -static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) +void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) {${cppPackageProviders} } @@ -217,7 +213,7 @@ static void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::C `; changesNecessary = - updateFile(cppHeaderFile, cppHeaderContents, verbose, checkMode) || + updateFile(cppFile, cppContents, verbose, checkMode) || changesNecessary; } From 25777475ef40a8e6114d62ab98b4ad9eb74f8dcf Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Tue, 2 Jun 2020 17:19:36 -0700 Subject: [PATCH 11/28] Addressing feedback: vstools function names and comments --- vnext/local-cli/runWindows/utils/vstools.js | 61 ++++++++++++++++----- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/vnext/local-cli/runWindows/utils/vstools.js b/vnext/local-cli/runWindows/utils/vstools.js index a89baa5f907..ff0b519c5f1 100644 --- a/vnext/local-cli/runWindows/utils/vstools.js +++ b/vnext/local-cli/runWindows/utils/vstools.js @@ -14,28 +14,55 @@ const projectTypeGuidsByLanguage = { cs: '{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}', }; -function hasBlock(lines, block) { - var startIndex = lines.indexOf(block[0]); - - if (startIndex >= 0) { - for (let i = 1; i < block.length; i++) { - if (lines[startIndex + i] !== block[i]) { - return false; +/** + * Checks is the given block of lines exists within an array of lines. + * @param {array} lines - The array of lines to search. + * @param {array} block - The block of lines to search for. + * @return {boolean} - True if the block of lines does exist within lines. + */ +function linesContainsBlock(lines, block) { + if (block.length > 0) { + var startIndex = lines.indexOf(block[0]); + + if (startIndex >= 0) { + for (let i = 1; i < block.length; i++) { + if (lines[startIndex + i] !== block[i]) { + return false; + } } + return true; } - return true; } return false; } -function insertBlock(lines, block, index) { +/** + * Insert the given block of lines into an array of lines. + * @param {array} lines - The array of lines to insert into. + * @param {array} block - The block of lines to insert. + * @param {number} index - The index to perform the insert. + */ +function insertBlockIntoLines(lines, block, index) { for (let i = 0; i < block.length; i++) { lines.splice(index + i, 0, block[i]); } } -function getBlockContents(lines, startLine, endLine, includeStartEnd = true) { +/** + * Search through an array of lines for a block of lines starting with startLine and ending with endLine. + * @param {array} lines - The array of lines to search. + * @param {string} startLine - The first line of the block. + * @param {string} endLine - The last line of the block. + * @param {boolean} includeStartEnd - Include the start and end lines in the result. + * @return {array} The found block of lines, if found. + */ +function getBlockContentsFromLines( + lines, + startLine, + endLine, + includeStartEnd = true, +) { const startIndex = lines.indexOf(startLine); const endIndex = lines.indexOf(endLine, startIndex); @@ -50,6 +77,14 @@ function getBlockContents(lines, startLine, endLine, includeStartEnd = true) { return []; } +/** + * Adds the necessary info from a VS project into a VS solution file so that it will build. + * @param {string} slnFile - The Absolute path to the target VS solution file. + * @param {object} project - The object representing the project info. + * @param {boolean} verbose - If true, enable verbose logging. + * @param {boolean} checkMode - It true, don't make any changes. + * @return {boolean} Whether any changes were necessary. + */ function addProjectToSolution( slnFile, project, @@ -85,19 +120,19 @@ function addProjectToSolution( 'EndProject', ]; - if (!hasBlock(slnLines, projectEntryBlock)) { + if (!linesContainsBlock(slnLines, projectEntryBlock)) { if (verbose) { console.log(chalk.yellow('Missing project entry block.')); } const globalIndex = slnLines.indexOf('Global'); - insertBlock(slnLines, projectEntryBlock, globalIndex); + insertBlockIntoLines(slnLines, projectEntryBlock, globalIndex); contentsChanged = true; } // Check for the project configuration platforms - const slnConfigs = getBlockContents( + const slnConfigs = getBlockContentsFromLines( slnLines, '\tGlobalSection(SolutionConfigurationPlatforms) = preSolution', '\tEndGlobalSection', From 606ae0973c392b45c7a871219f93d13b06830197 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Wed, 3 Jun 2020 10:46:47 -0700 Subject: [PATCH 12/28] Addressing feedback: switching to template files --- .../react-native.config.js | 2 +- .../AutolinkedNativeModules.g.cpp | 2 +- packages/playground/react-native.config.js | 2 +- .../AutolinkedNativeModules.g.cpp | 2 +- .../playground/AutolinkedNativeModules.g.cpp | 2 +- vnext/local-cli/generator-common/index.js | 21 +++- vnext/local-cli/generator-windows/index.js | 5 + .../cpp/src/AutolinkedNativeModules.g.cpp | 6 +- .../cpp/src/AutolinkedNativeModules.g.h | 2 +- .../cpp/src/AutolinkedNativeModules.g.targets | 6 + .../cs/src/AutolinkedNativeModules.g.cs | 4 +- .../cs/src/AutolinkedNativeModules.g.targets | 6 + vnext/local-cli/runWindows/utils/autolink.js | 106 +++++++++--------- 13 files changed, 99 insertions(+), 67 deletions(-) create mode 100644 vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.targets create mode 100644 vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.targets diff --git a/packages/microsoft-reactnative-sampleapps/react-native.config.js b/packages/microsoft-reactnative-sampleapps/react-native.config.js index 5387368f7e6..40aa409f329 100644 --- a/packages/microsoft-reactnative-sampleapps/react-native.config.js +++ b/packages/microsoft-reactnative-sampleapps/react-native.config.js @@ -1,6 +1,6 @@ module.exports = { reactNativePath: '../../vnext', - // Uncomment the below when running autolink-windows for SampleAppCS + // Uncomment the below then run `npx react-native autolink-windows` to run autolink for SampleAppCS // project: { // windows: { // sourceDir: 'windows', diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp index 7d268b12944..e363d21605e 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp @@ -1,8 +1,8 @@ // AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +// clang-format off #include "pch.h" #include "AutolinkedNativeModules.g.h" -// clang-format off namespace winrt::Microsoft::ReactNative { diff --git a/packages/playground/react-native.config.js b/packages/playground/react-native.config.js index 5c1ea6bd594..8645017e1cf 100644 --- a/packages/playground/react-native.config.js +++ b/packages/playground/react-native.config.js @@ -1,5 +1,5 @@ module.exports = { - // Uncomment the below when running autolink-windows for playground-win32 + // Uncomment the below then run `npx react-native autolink-windows` to run autolink for playground-win32 // project: { // windows: { // sourceDir: 'windows', diff --git a/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp index 7d268b12944..e363d21605e 100644 --- a/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp +++ b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp @@ -1,8 +1,8 @@ // AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +// clang-format off #include "pch.h" #include "AutolinkedNativeModules.g.h" -// clang-format off namespace winrt::Microsoft::ReactNative { diff --git a/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp b/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp index 7d268b12944..e363d21605e 100644 --- a/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp +++ b/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp @@ -1,8 +1,8 @@ // AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +// clang-format off #include "pch.h" #include "AutolinkedNativeModules.g.h" -// clang-format off namespace winrt::Microsoft::ReactNative { diff --git a/vnext/local-cli/generator-common/index.js b/vnext/local-cli/generator-common/index.js index a07e01688db..7151175e3a1 100644 --- a/vnext/local-cli/generator-common/index.js +++ b/vnext/local-cli/generator-common/index.js @@ -155,6 +155,20 @@ function walk(current/*: string*/)/*: string[]*/ { return result.concat.apply([current], files); } +/** + * Get a source file and replace parts of its contents. + * @param {string} srcPath Path to the source file. + * @param {object} replacements e.g. {'TextToBeReplaced': 'Replacement'} + * @return The contents of the file with the replacements applied. + */ +function resolveContents(srcPath, replacements) { + let content = fs.readFileSync(srcPath, 'utf8'); + Object.keys(replacements).forEach(regex => { + content = content.replace(new RegExp(regex, 'g'), replacements[regex]); + }); + return content; +} + // Binary files, don't process these (avoid decoding as utf8) const binaryExtensions = ['.png', '.jar', '.keystore']; @@ -223,10 +237,7 @@ function copyAndReplace( } else { // Text file const srcPermissions = fs.statSync(srcPath).mode; - let content = fs.readFileSync(srcPath, 'utf8'); - Object.keys(replacements).forEach(regex => { - content = content.replace(new RegExp(regex, 'g'), replacements[regex]); - }); + let content = resolveContents(srcPath, replacements); let shouldOverwrite = 'overwrite'; if (contentChangedCallback) { @@ -385,5 +396,5 @@ function upgradeFileContentChangedCallback( } module.exports = { - createDir, copyAndReplaceWithChangedCallback, copyAndReplaceAll, + createDir, resolveContents, copyAndReplaceWithChangedCallback, copyAndReplaceAll, }; diff --git a/vnext/local-cli/generator-windows/index.js b/vnext/local-cli/generator-windows/index.js index 115255b498a..ba7bbe2d237 100644 --- a/vnext/local-cli/generator-windows/index.js +++ b/vnext/local-cli/generator-windows/index.js @@ -121,6 +121,11 @@ function copyProjectTemplateAndReplace( '<%=XamlNugetPkgVersion%>': xamlNugetPkgVersion, '<%=XamlNamespace%>': xamlNamespace, '<%=XamlNamespaceCpp%>': xamlNamespaceCpp, + '<%=AutolinkProjectReferencesForTargets%>': '', + '<%=AutolinkCsUsingNamespaces%>': '', + '<%=AutolinkCsReactPacakgeProviders%>': '', + '<%=AutolinkCppIncludes%>': '', + '<%=AutolinkCppPackageProviders%>': '', }; [ diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp index 7d268b12944..6e5f3c2cc24 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp +++ b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.cpp @@ -1,13 +1,13 @@ // AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" +// clang-format off #include "pch.h" -#include "AutolinkedNativeModules.g.h" +#include "AutolinkedNativeModules.g.h"<%=AutolinkCppIncludes%> -// clang-format off namespace winrt::Microsoft::ReactNative { void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ +{<%=AutolinkCppPackageProviders%> } } diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h index 99c3efc78bd..f28bb8be361 100644 --- a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h +++ b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.h @@ -1,7 +1,7 @@ // AutolinkedNativeModules.g.h contents generated by "react-native autolink-windows" +// clang-format off #pragma once -// clang-format off namespace winrt::Microsoft::ReactNative { diff --git a/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.targets b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.targets new file mode 100644 index 00000000000..044846f438b --- /dev/null +++ b/vnext/local-cli/generator-windows/templates/cpp/src/AutolinkedNativeModules.g.targets @@ -0,0 +1,6 @@ + + + + <%=AutolinkProjectReferencesForTargets%> + + diff --git a/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs b/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs index fd512bbb55e..785576278ff 100644 --- a/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs +++ b/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.cs @@ -1,13 +1,13 @@ // AutolinkedNativeModules.g.cs contents generated by "react-native autolink-windows" -using System.Collections.Generic; +using System.Collections.Generic;<%=AutolinkCsUsingNamespaces%> namespace Microsoft.ReactNative.Managed { internal static class AutolinkedNativeModules { internal static void RegisterAutolinkedNativeModulePackages(IList packageProviders) - { + {<%=AutolinkCsReactPacakgeProviders%> } } } diff --git a/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.targets b/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.targets new file mode 100644 index 00000000000..044846f438b --- /dev/null +++ b/vnext/local-cli/generator-windows/templates/cs/src/AutolinkedNativeModules.g.targets @@ -0,0 +1,6 @@ + + + + <%=AutolinkProjectReferencesForTargets%> + + diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index 11cab26b6c5..70094647d59 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -12,6 +12,9 @@ const chalk = require('chalk'); const {newSpinner} = require('./commandWithProgress'); const vstools = require('./vstools'); +const generatorCommon = require('../../generator-common'); + +const templateRoot = path.join(__dirname, '../../generator-windows/templates'); function verboseMessage(message, verbose) { if (verbose) { @@ -19,14 +22,20 @@ function verboseMessage(message, verbose) { } } +function getNormalizedContents(srcFile, replacements) { + // Template files are CRLF, JS-generated replacements are LF, normalize replacements to CRLF + for (var key in replacements) { + replacements[key] = replacements[key].replace(/\n/g, '\r\n'); + } + + return generatorCommon.resolveContents(srcFile, replacements); +} + function updateFile(filePath, expectedContents, verbose, checkMode) { const fileName = chalk.bold(path.basename(filePath)); verboseMessage(`Reading ${fileName}...`, verbose); const actualContents = fs.existsSync(filePath) - ? fs - .readFileSync(filePath) - .toString() - .replace(/\r\n/g, '\n') + ? fs.readFileSync(filePath).toString() : ''; const contentsChanged = expectedContents !== actualContents; @@ -35,7 +44,7 @@ function updateFile(filePath, expectedContents, verbose, checkMode) { verboseMessage(chalk.yellow(`${fileName} needs to be updated.`), verbose); if (!checkMode) { verboseMessage(`Writing ${fileName}...`, verbose); - fs.writeFileSync(filePath, expectedContents.replace(/\n/g, '\r\n'), { + fs.writeFileSync(filePath, expectedContents, { encoding: 'utf8', flag: 'w', }); @@ -145,30 +154,24 @@ async function updateAutoLink(config, args, options) { }); } - const csFile = path.join(projectDir, 'AutolinkedNativeModules.g.cs'); + const csFileName = 'AutolinkedNativeModules.g.cs'; + + const srcCsFile = path.join(templateRoot, projectLang, 'src', csFileName); + + const destCsFile = path.join(projectDir, csFileName); verboseMessage( - `Calculating ${chalk.bold(path.basename(csFile))}...`, + `Calculating ${chalk.bold(path.basename(destCsFile))}...`, verbose, ); - const csContents = `// AutolinkedNativeModules.g.cs contents generated by "react-native autolink-windows" - -using System.Collections.Generic;${csUsingNamespaces} - -namespace Microsoft.ReactNative.Managed -{ - internal static class AutolinkedNativeModules - { - internal static void RegisterAutolinkedNativeModulePackages(IList packageProviders) - {${csReactPacakgeProviders} - } - } -} -`; + const csContents = getNormalizedContents(srcCsFile, { + '<%=AutolinkCsUsingNamespaces%>': csUsingNamespaces, + '<%=AutolinkCsReactPacakgeProviders%>': csReactPacakgeProviders, + }); changesNecessary = - updateFile(csFile, '\uFEFF' + csContents, verbose, checkMode) || + updateFile(destCsFile, csContents, verbose, checkMode) || changesNecessary; } else if (projectLang === 'cpp') { let cppIncludes = ''; @@ -190,30 +193,29 @@ namespace Microsoft.ReactNative.Managed }); } - const cppFile = path.join(projectDir, 'AutolinkedNativeModules.g.cpp'); + const cppFileName = 'AutolinkedNativeModules.g.cpp'; - verboseMessage( - `Calculating ${chalk.bold(path.basename(cppFile))}...`, - verbose, + const srcCppFile = path.join( + templateRoot, + projectLang, + 'src', + cppFileName, ); - const cppContents = `// AutolinkedNativeModules.g.cpp contents generated by "react-native autolink-windows" -#include "pch.h" -#include "AutolinkedNativeModules.g.h"${cppIncludes} - -// clang-format off -namespace winrt::Microsoft::ReactNative -{ + const destCppFile = path.join(projectDir, cppFileName); -void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{${cppPackageProviders} -} + verboseMessage( + `Calculating ${chalk.bold(path.basename(destCppFile))}...`, + verbose, + ); -} -`; + const cppContents = getNormalizedContents(srcCppFile, { + '<%=AutolinkCppIncludes%>': cppIncludes, + '<%=AutolinkCppPackageProviders%>': cppPackageProviders, + }); changesNecessary = - updateFile(cppFile, cppContents, verbose, checkMode) || + updateFile(destCppFile, cppContents, verbose, checkMode) || changesNecessary; } @@ -242,26 +244,28 @@ void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collecti }); } - const targetFile = path.join( - projectDir, - 'AutolinkedNativeModules.g.targets', + const targetFileName = 'AutolinkedNativeModules.g.targets'; + + const srcTargetFile = path.join( + templateRoot, + projectLang, + 'src', + targetFileName, ); + const destTargetFile = path.join(projectDir, targetFileName); + verboseMessage( - `Calculating ${chalk.bold(path.basename(targetFile))}...`, + `Calculating ${chalk.bold(path.basename(destTargetFile))}...`, verbose, ); - const targetContents = ` - - - ${projectReferencesForTargets} - - -`; + const targetContents = getNormalizedContents(srcTargetFile, { + '<%=AutolinkProjectReferencesForTargets%>': projectReferencesForTargets, + }); changesNecessary = - updateFile(targetFile, targetContents, verbose, checkMode) || + updateFile(destTargetFile, targetContents, verbose, checkMode) || changesNecessary; // Generating project entries for solution From 95aad101bb863b25236e3742a981bf9de0429e4f Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Wed, 3 Jun 2020 10:54:34 -0700 Subject: [PATCH 13/28] Addressing feedback: fix cpp/cs namespace normalization --- vnext/local-cli/config/dependencyConfig.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 2f5ade0db3a..1bf87122974 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -134,8 +134,8 @@ function dependencyConfigWindows(folder, userConfig = {}) { const directDependency = true; - const cppNamespace = projectNamespace.replace('.', '::'); - const csNamespace = projectNamespace.replace('::', '.'); + const cppNamespace = projectNamespace.replace(/\./g, '::'); + const csNamespace = projectNamespace.replace(/::/g, '.'); const cppHeaders = [`winrt/${csNamespace}.h`]; const cppPackageProviders = [`${cppNamespace}::ReactPackageProvider`]; From 264c487dfe0183caff92eb35ab054d897a255f9f Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Wed, 3 Jun 2020 12:10:18 -0700 Subject: [PATCH 14/28] Addressing feedback: add timer to measure autolink performance --- vnext/local-cli/runWindows/utils/autolink.js | 27 +++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index 70094647d59..a9bc90a1a08 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -9,6 +9,7 @@ const execSync = require('child_process').execSync; const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); +const performance = require('perf_hooks').performance; const {newSpinner} = require('./commandWithProgress'); const vstools = require('./vstools'); @@ -68,6 +69,8 @@ function exitProcessWithStatusCode(statusCode, loggingWasEnabled) { } async function updateAutoLink(config, args, options) { + const startTime = performance.now(); + const verbose = options.logging; const checkMode = options.check; @@ -305,10 +308,15 @@ async function updateAutoLink(config, args, options) { }); spinner.succeed(); + var endTime = performance.now(); if (!changesNecessary) { console.log( - `${chalk.green('Success:')} No auto-linking changes necessary.`, + `${chalk.green( + 'Success:', + )} No auto-linking changes necessary. (${Math.round( + endTime - startTime, + )}ms)`, ); } else if (checkMode) { console.log( @@ -318,15 +326,26 @@ async function updateAutoLink(config, args, options) { '--check', )} specified. Run ${chalk.bold( "'npx react-native autolink-windows'", - )} to apply the changes.`, + )} to apply the changes. (${Math.round(endTime - startTime)}ms)`, ); exitProcessWithStatusCode(0, verbose); } else { - console.log(`${chalk.green('Success:')} Auto-linking changes completed.`); + console.log( + `${chalk.green( + 'Success:', + )} Auto-linking changes completed. (${Math.round( + endTime - startTime, + )}ms)`, + ); } } catch (e) { spinner.fail(); - console.log(chalk.red('Error:') + ' ' + e.toString()); + var endTime = performance.now(); + console.log( + `${chalk.red('Error:')} ${e.toString()}. (${Math.round( + endTime - startTime, + )}ms)`, + ); exitProcessWithStatusCode(1, verbose); } } From e7307699017344730850428b5bbd293227002b42 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 4 Jun 2020 11:34:10 -0700 Subject: [PATCH 15/28] Addressing feedback: update config heurestics --- .../react-native.config.js | 23 +-- packages/playground/react-native.config.js | 23 +-- vnext/local-cli/config/configUtils.js | 143 +++++++++++------- vnext/local-cli/config/dependencyConfig.js | 134 +++++++++------- vnext/local-cli/config/projectConfig.js | 68 +++++++-- vnext/local-cli/runWindows/utils/autolink.js | 4 +- 6 files changed, 258 insertions(+), 137 deletions(-) diff --git a/packages/microsoft-reactnative-sampleapps/react-native.config.js b/packages/microsoft-reactnative-sampleapps/react-native.config.js index 40aa409f329..24587b25a88 100644 --- a/packages/microsoft-reactnative-sampleapps/react-native.config.js +++ b/packages/microsoft-reactnative-sampleapps/react-native.config.js @@ -1,13 +1,16 @@ +// Change the below to true for autolink to target SampleAppCS instead of SampleAppCPP. +// Then run `npx react-native autolink-windows` to actually run the autolink. +const targetCS = false; + module.exports = { reactNativePath: '../../vnext', - // Uncomment the below then run `npx react-native autolink-windows` to run autolink for SampleAppCS - // project: { - // windows: { - // sourceDir: 'windows', - // solutionFile: 'SampleApps.sln', - // project: { - // projectFile: 'SampleAppCS\\SampleAppCS.csproj', - // }, - // }, - // }, + project: { + windows: { + sourceDir: 'windows', + solutionFile: 'SampleApps.sln', + project: { + projectFile: targetCS ? 'SampleAppCS\\SampleAppCS.csproj' : 'SampleAppCPP\\SampleAppCPP.vcxproj', + }, + }, + }, }; diff --git a/packages/playground/react-native.config.js b/packages/playground/react-native.config.js index 8645017e1cf..5720987bf24 100644 --- a/packages/playground/react-native.config.js +++ b/packages/playground/react-native.config.js @@ -1,12 +1,15 @@ +// Change the below to true for autolink to target playground-win32 instead of playground. +// Then run `npx react-native autolink-windows` to actually run the autolink. +const targetWin32 = false; + module.exports = { - // Uncomment the below then run `npx react-native autolink-windows` to run autolink for playground-win32 - // project: { - // windows: { - // sourceDir: 'windows', - // solutionFile: 'playground-win32.sln', - // project: { - // projectFile: 'playground-win32\\Playground-win32.vcxproj', - // }, - // }, - // }, + project: { + windows: { + sourceDir: 'windows', + solutionFile: targetWin32 ? 'playground-win32.sln' : 'playground.sln', + project: { + projectFile: targetWin32 ? 'playground-win32\\playground-win32.vcxproj' : 'playground\\playground.vcxproj', + }, + }, + }, }; diff --git a/vnext/local-cli/config/configUtils.js b/vnext/local-cli/config/configUtils.js index e0d4e60607b..856bebde7e6 100644 --- a/vnext/local-cli/config/configUtils.js +++ b/vnext/local-cli/config/configUtils.js @@ -9,9 +9,6 @@ const fs = require('fs'); const path = require('path'); const glob = require('glob'); -const xmldoc = require('xmldoc'); - -// find VS files matching the pattern function findFiles(folder, filenamePattern) { const files = glob.sync(path.join('**', filenamePattern), { cwd: folder, @@ -27,7 +24,6 @@ function findFiles(folder, filenamePattern) { return files; } -// finds the windows folder if present function findWindowsFolder(folder) { const winDir = 'windows'; const joinedDir = path.join(folder, winDir); @@ -38,64 +34,100 @@ function findWindowsFolder(folder) { return null; } -// find the visual studio solution file -function findSolutionFile(winFolder, packageJson = {}) { - if (packageJson.name) { - const solutionName = packageJson.name; +function isRnwSolution(filePath) { + return ( + fs + .readFileSync(filePath) + .toString() + .search(/ReactNative/) > 0 + ); +} - // First look for a solution named solutionName.sln - const slnFile = findFiles(winFolder, solutionName + '.sln')[0]; +function findSolutionFiles(winFolder) { + // First search for all potential solution files + const allSolutions = findFiles(winFolder, '*.sln'); - if (slnFile) { - return path.join(winFolder, slnFile); - } + if (allSolutions.length === 0) { + // If there're no solution files, return 0 + return []; } - // Next look for any solution *.sln - const allSln = findFiles(winFolder, '*.sln'); + var solutionFiles = []; - if (allSln.length > 0) { - return path.join(winFolder, allSln[0]); + // Try to find any solution file that appears to be a react native solution + for (var solutionFile of allSolutions) { + if (isRnwSolution(path.join(winFolder, solutionFile))) { + solutionFiles.push(solutionFile); + } } - return null; + return solutionFiles; } -// find the visual studio project file -function findProjectFile(winFolder, packageJson = {}) { - if (packageJson.name) { - const projectName = packageJson.name; +function isRnwDependencyProject(filePath) { + return ( + fs + .readFileSync(filePath) + .toString() + .search(/Microsoft\.ReactNative\.Uwp\.(Cpp|CSharp)Lib\.targets/) > 0 + ); +} - // First look for a project named projectName.vcxproj - const cppProj = findFiles(winFolder, projectName + '.vcxproj')[0]; +function findDependencyProjectFiles(winFolder) { + // First, search for all potential project files + const allCppProj = findFiles(winFolder, '*.vcxproj'); + const allCsProj = findFiles(winFolder, '*.csproj'); - if (cppProj) { - return path.join(winFolder, cppProj); - } + const allProjects = allCppProj.concat(allCsProj); - // Next look for a project named projectName.csproj - const csProj = findFiles(winFolder, projectName + '.csproj')[0]; + if (allProjects.length === 0) { + // If there're no project files, return 0 + return []; + } - if (csProj) { - return path.join(winFolder, csProj); + var dependencyProjectFiles = []; + + // Try to find any project file that appears to be a dependency project + for (var projectFile of allProjects) { + if (isRnwDependencyProject(path.join(winFolder, projectFile))) { + dependencyProjectFiles.push(projectFile); } } - // Next look for any project *.vcxproj + return dependencyProjectFiles; +} + +function isRnwAppProject(filePath) { + return ( + fs + .readFileSync(filePath) + .toString() + .search(/Microsoft\.ReactNative\.Uwp\.(Cpp|CSharp)App\.targets/) > 0 + ); +} + +function findAppProjectFiles(winFolder) { + // First, search for all potential project files const allCppProj = findFiles(winFolder, '*.vcxproj'); + const allCsProj = findFiles(winFolder, '*.csproj'); + + const allProjects = allCppProj.concat(allCsProj); - if (allCppProj.length > 0) { - return path.join(winFolder, allCppProj[0]); + if (allProjects.length === 0) { + // If there're no project files, return 0 + return []; } - // Next look for any project *.csproj - const allCsProj = findFiles(winFolder, '*.csproj'); + var appProjectFiles = []; - if (allCsProj.length > 0) { - return path.join(winFolder, allCsProj[0]); + // Try to find any project file that appears to be an app project + for (var projectFile of allProjects) { + if (isRnwAppProject(path.join(winFolder, projectFile))) { + appProjectFiles.push(projectFile); + } } - return null; + return appProjectFiles; } function getProjectLanguage(projectPath) { @@ -107,35 +139,42 @@ function getProjectLanguage(projectPath) { return null; } -// read visual studio project file which is actually a XML doc function readProjectFile(projectPath) { - return new xmldoc.XmlDocument(fs.readFileSync(projectPath, 'utf8')); + return fs.readFileSync(projectPath, 'utf8').toString(); } -function findProperty(projectXml, propertyName) { - return projectXml.valueWithPath('PropertyGroup.' + propertyName); +function findTagValue(projectContents, tagName) { + const regexExpression = `<${tagName}>(.*)`; + const regex = new RegExp(regexExpression, 'm'); + const match = projectContents.match(regex); + + return match !== null && match.length === 2 ? match[1] : null; } -function getProjectName(projectXml) { +function getProjectName(projectContents) { return ( - findProperty(projectXml, 'ProjectName') || - findProperty(projectXml, 'AssemblyName') + findTagValue(projectContents, 'ProjectName') || + findTagValue(projectContents, 'AssemblyName') ); } -function getProjectNamespace(projectXml) { - return findProperty(projectXml, 'RootNamespace'); +function getProjectNamespace(projectContents) { + return findTagValue(projectContents, 'RootNamespace'); } -function getProjectGuid(projectXml) { - return findProperty(projectXml, 'ProjectGuid'); +function getProjectGuid(projectContents) { + return findTagValue(projectContents, 'ProjectGuid'); } module.exports = { findFiles: findFiles, findWindowsFolder: findWindowsFolder, - findSolutionFile: findSolutionFile, - findProjectFile: findProjectFile, + isRnwSolution: isRnwSolution, + findSolutionFiles: findSolutionFiles, + isRnwDependencyProject: isRnwDependencyProject, + findDependencyProjectFiles: findDependencyProjectFiles, + isRnwAppProject: isRnwAppProject, + findAppProjectFiles: findAppProjectFiles, getProjectLanguage: getProjectLanguage, readProjectFile: readProjectFile, getProjectName: getProjectName, diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 1bf87122974..450a9675349 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -25,8 +25,8 @@ opt - Item is optional. If an override file exists, it MAY provide it. If no ov { folder: string, // (auto) Absolute path to the module root folder, determined by react-native config, ex: 'c:\path\to\app-name\node_modules\module-name' - sourceDir: string, // (req) Relative path to the windows implementation under folder, ex: 'windows' - solutionFile: string, // (req) Relative path to the module's VS solution file under sourceDir, ex: 'ModuleName.sln' + sourceDir: string, // (opt, req if projects defined) Relative path to the windows implementation under folder, ex: 'windows' + solutionFile: string, // (opt) Relative path to the module's VS solution file under sourceDir, ex: 'ModuleName.sln' projects: [ // (opt) Array of VS projects that must be added to the consuming app's solution file, so they are built { projectFile: string, // (req) Relative path to the VS project file under sourceDir, ex: 'ProjectName\ProjectName.vcxproj' for 'c:\path\to\app-name\node_modules\module-name\windows\ProjectName\ProjectName.vcxproj' @@ -55,30 +55,54 @@ opt - Item is optional. If an override file exists, it MAY provide it. If no ov */ function dependencyConfigWindows(folder, userConfig = {}) { - const usingManualOverride = 'sourceDir' in userConfig; + const usingManualProjectsOverride = 'projects' in userConfig; - const sourceDir = usingManualOverride - ? path.join(folder, userConfig.sourceDir) - : configUtils.findWindowsFolder(folder); + var projects = usingManualProjectsOverride ? userConfig.projects : []; - if (sourceDir === null) { - return null; - } + const usingManualNugetPackagesOverride = 'nugetPackages' in userConfig; - if (usingManualOverride && !('solutionFile' in userConfig)) { - throw 'solutionFile is required but not specified in react-native.config'; - } + var nugetPackages = usingManualNugetPackagesOverride + ? userConfig.nugetPackages + : []; - const packageJson = require(path.join(folder, 'package.json')); + var sourceDir = null; + if (usingManualProjectsOverride && projects.length > 0) { + // Manaully provided projects, so extract the sourceDir + if (!('sourceDir' in userConfig) || userConfig.sourceDir === null) { + throw new Error( + 'sourceDir is required if projects are specified, but it is not specified in react-native.config', + ); + } + sourceDir = path.join(folder, userConfig.sourceDir); + } else if (!usingManualProjectsOverride) { + // No manually provided projects, try to find sourceDir + sourceDir = configUtils.findWindowsFolder(folder); + } - const solutionFile = usingManualOverride - ? path.join(sourceDir, userConfig.solutionFile) - : configUtils.findSolutionFile(sourceDir, packageJson); + if ( + sourceDir === null && + projects.length === 0 && + nugetPackages.length === 0 + ) { + // Nothing to do here, bail + return null; + } - var projects = usingManualOverride ? userConfig.projects || [] : []; - var nugetPackages = usingManualOverride ? userConfig.nugetPackages || [] : []; + const usingManualSolutionFile = 'solutionFile' in userConfig; + + var solutionFile = null; + if (usingManualSolutionFile && userConfig.solutionFile !== null) { + // Manually provided solutionFile, so extract it + solutionFile = path.join(sourceDir, userConfig.solutionFile); + } else if (!usingManualSolutionFile) { + // No manually provided solutionFile, try to find it + const solutionFiles = configUtils.findSolutionFiled(sourceDir); + if (solutionFiles.length === 1) { + solutionFile = path.join(sourceDir, solutionFile); + } + } - if (usingManualOverride) { + if (usingManualProjectsOverride) { // react-native.config used, fill out (auto) items for each provided project, verify (req) items are present const alwaysRequired = ['projectFile', 'directDependency']; @@ -94,71 +118,79 @@ function dependencyConfigWindows(folder, userConfig = {}) { // Verifying (req) items alwaysRequired.forEach(item => { if (!(item in projects[i])) { - throw `${item} is required for each project in react-native.config`; + throw new Error( + `${item} is required for each project in react-native.config`, + ); } }); const projectFile = path.join(sourceDir, projects[i].projectFile); - const projectXml = configUtils.readProjectFile(projectFile); + const projectContents = configUtils.readProjectFile(projectFile); // Calculating (auto) items - - projects[i].projectName = configUtils.getProjectName(projectXml); + projects[i].projectName = configUtils.getProjectName(projectContents); projects[i].projectLang = configUtils.getProjectLanguage(projectFile); - projects[i].projectGuid = configUtils.getProjectGuid(projectXml); + projects[i].projectGuid = configUtils.getProjectGuid(projectContents); if (projects[i].directDependency) { // Verifying (req) items requiredForDirectDepenencies.forEach(item => { if (!(item in projects[i])) { - throw `${item} is required for each project in react-native.config`; + throw new Error( + `${item} is required for each project in react-native.config`, + ); } }); } } } else { - // No react-native.config, try to heurestically find the correct project + // No react-native.config, try to heurestically find any projects - const projectFile = configUtils.findProjectFile(sourceDir, packageJson); + const foundProjects = configUtils.findDependencyProjectFiles(sourceDir); - const projectLang = configUtils.getProjectLanguage(projectFile); + for (let i = 0; i < foundProjects.length; i++) { + const projectFile = path.join(sourceDir, foundProjects[i]); - const projectXml = configUtils.readProjectFile(projectFile); + const projectLang = configUtils.getProjectLanguage(projectFile); - const projectName = configUtils.getProjectName(projectXml); + const projectContents = configUtils.readProjectFile(projectFile); - const projectGuid = configUtils.getProjectGuid(projectXml); + const projectName = configUtils.getProjectName(projectContents); - const projectNamespace = configUtils.getProjectNamespace(projectXml); + const projectGuid = configUtils.getProjectGuid(projectContents); - const directDependency = true; + const projectNamespace = configUtils.getProjectNamespace(projectContents); - const cppNamespace = projectNamespace.replace(/\./g, '::'); - const csNamespace = projectNamespace.replace(/::/g, '.'); + const directDependency = true; - const cppHeaders = [`winrt/${csNamespace}.h`]; - const cppPackageProviders = [`${cppNamespace}::ReactPackageProvider`]; - const csNamespaces = [`${csNamespace}`]; - const csPackageProviders = [`${csNamespace}.ReactPackageProvider`]; + const cppNamespace = projectNamespace.replace(/\./g, '::'); + const csNamespace = projectNamespace.replace(/::/g, '.'); - projects.push({ - projectFile: projectFile.substr(sourceDir.length + 1), - projectName, - projectLang, - projectGuid, - directDependency, - cppHeaders, - cppPackageProviders, - csNamespaces, - csPackageProviders, - }); + const cppHeaders = [`winrt/${csNamespace}.h`]; + const cppPackageProviders = [`${cppNamespace}::ReactPackageProvider`]; + const csNamespaces = [`${csNamespace}`]; + const csPackageProviders = [`${csNamespace}.ReactPackageProvider`]; + + projects.push({ + projectFile: projectFile.substr(sourceDir.length + 1), + projectName, + projectLang, + projectGuid, + directDependency, + cppHeaders, + cppPackageProviders, + csNamespaces, + csPackageProviders, + }); + } } return { folder, sourceDir: sourceDir.substr(folder.length + 1), - solutionFile: solutionFile.substr(sourceDir.length + 1), + solutionFile: + solutionFile !== null ? solutionFile.substr(sourceDir.length + 1) : null, projects, nugetPackages, }; diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index d5e9962b8d5..9f43b579fc7 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -54,26 +54,68 @@ function projectConfigWindows(folder, userConfig = {}) { // Verifying (req) items alwaysRequired.forEach(item => { if (!(item in userConfig)) { - throw `${item} is required but not specified in react-native.config`; + throw new Error( + `${item} is required but not specified in react-native.config`, + ); } }); } - const packageJson = require(path.join(folder, 'package.json')); + const usingManualSolutionFile = 'solutionFile' in userConfig; + + var solutionFile = null; + if (usingManualSolutionFile && userConfig.solutionFile !== null) { + // Manually provided solutionFile, so extract it + solutionFile = path.join(sourceDir, userConfig.solutionFile); + } else if (!usingManualSolutionFile) { + // No manually provided solutionFile, try to find it + const foundSolutions = configUtils.findSolutionFiles(sourceDir); + if (foundSolutions.length === 0) { + throw new Error( + 'No app solution file found, please specify in react-native.config', + ); + } else if (foundSolutions.length > 1) { + throw new Error( + 'Too many app solution files found, please specify in react-native.config', + ); + } + solutionFile = path.join(sourceDir, foundSolutions[0]); + } - const solutionFile = usingManualOverride - ? path.join(sourceDir, userConfig.solutionFile) - : configUtils.findSolutionFile(sourceDir, packageJson); + if (solutionFile === null) { + throw new Error( + 'Unable to determine app solution file, please specify in react-native.config', + ); + } - if (usingManualOverride && !('projectFile' in userConfig.project)) { - throw 'projectFile is required but not specified in react-native.config'; + const usingManualProjectOverride = 'project' in userConfig; + + var projectFile = null; + if (usingManualProjectOverride) { + // Manually provided project, so extract it + projectFile = path.join(sourceDir, userConfig.project.projectFile); + } else { + // No manually provided project, try to find it + const foundProjects = configUtils.findAppProjectFiles(sourceDir); + if (foundProjects.length === 0) { + throw new Error( + 'No app project file found, please specify in react-native.config', + ); + } else if (foundProjects.length > 1) { + throw new Error( + 'Too many app project files found, please specify in react-native.config', + ); + } + projectFile = path.join(sourceDir, foundProjects[0]); } - const projectFile = usingManualOverride - ? path.join(sourceDir, userConfig.project.projectFile) - : configUtils.findProjectFile(sourceDir, packageJson); + if (projectFile === null) { + throw new Error( + 'Unable to determine app project file, please specify in react-native.config', + ); + } - const projectXml = configUtils.readProjectFile(projectFile); + const projectContents = configUtils.readProjectFile(projectFile); return { folder, @@ -81,9 +123,9 @@ function projectConfigWindows(folder, userConfig = {}) { solutionFile: solutionFile.substr(sourceDir.length + 1), project: { projectFile: projectFile.substr(sourceDir.length + 1), - projectName: configUtils.getProjectName(projectXml), + projectName: configUtils.getProjectName(projectContents), projectLang: configUtils.getProjectLanguage(projectFile), - projectGuid: configUtils.getProjectGuid(projectXml), + projectGuid: configUtils.getProjectGuid(projectContents), }, }; } diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index a9bc90a1a08..e009c99a337 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -98,7 +98,9 @@ async function updateAutoLink(config, args, options) { const windowsAppConfig = rnConfig.project.windows; if (!windowsAppConfig) { - throw 'Windows auto-link only supported on windows app projects.'; + throw new Error( + 'Windows auto-link only supported on windows app projects.', + ); } verboseMessage('Found Windows app project, parsing...', verbose); From d8b9cde1097049ab1403ed66ef9bb26d68b889ae Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 4 Jun 2020 14:22:17 -0700 Subject: [PATCH 16/28] Fixing propagation of run-windows --no-autolink to skip check during build --- vnext/PropertySheets/Autolink.props | 1 + vnext/PropertySheets/Autolink.targets | 2 +- .../External/Microsoft.ReactNative.Uwp.CSharpApp.targets | 1 + .../External/Microsoft.ReactNative.Uwp.CppApp.targets | 1 + vnext/local-cli/runWindows/runWindows.js | 7 ++++++- 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/vnext/PropertySheets/Autolink.props b/vnext/PropertySheets/Autolink.props index 18a248ce190..f92142705b9 100644 --- a/vnext/PropertySheets/Autolink.props +++ b/vnext/PropertySheets/Autolink.props @@ -6,6 +6,7 @@ + true npx react-native autolink-windows --check $([MSBuild]::GetDirectoryNameOfFileAbove($(ProjectDir), 'package.json')) diff --git a/vnext/PropertySheets/Autolink.targets b/vnext/PropertySheets/Autolink.targets index 924c3f6d5a8..6a17e61ba4e 100644 --- a/vnext/PropertySheets/Autolink.targets +++ b/vnext/PropertySheets/Autolink.targets @@ -4,7 +4,7 @@ Licensed under the MIT License.. --> - + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets index 449b86ff3cb..8df20fda068 100644 --- a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets @@ -29,6 +29,7 @@ + diff --git a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets index 6bbf571b279..46901df229f 100644 --- a/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets +++ b/vnext/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets @@ -22,6 +22,7 @@ + diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index 904e2865ebb..7ee5fb06d34 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -75,7 +75,12 @@ async function runWindows(config, args, options) { // Get build/deploy options const buildType = deploy.getBuildConfiguration(options); - const msBuildProps = build.parseMsBuildProps(options); + var msBuildProps = build.parseMsBuildProps(options); + + if (!options.autolink) { + // Disable the autolink check if --no-autolink was passed + msBuildProps.RunAutolinkCheck = 'false'; + } try { await build.buildSolution( From 0efe81c8eb0b28e9d47b5975768fc00b47b6d76c Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Fri, 5 Jun 2020 11:53:47 -0700 Subject: [PATCH 17/28] Addressing feedback: JSDoc comments --- vnext/local-cli/config/configUtils.js | 72 ++++++++++++++++++++ vnext/local-cli/config/dependencyConfig.js | 6 ++ vnext/local-cli/config/projectConfig.js | 6 ++ vnext/local-cli/runWindows/utils/autolink.js | 30 ++++++++ vnext/local-cli/runWindows/utils/vstools.js | 28 ++++---- 5 files changed, 128 insertions(+), 14 deletions(-) diff --git a/vnext/local-cli/config/configUtils.js b/vnext/local-cli/config/configUtils.js index 856bebde7e6..15dd3bf1676 100644 --- a/vnext/local-cli/config/configUtils.js +++ b/vnext/local-cli/config/configUtils.js @@ -9,6 +9,12 @@ const fs = require('fs'); const path = require('path'); const glob = require('glob'); +/** + * Search for files matching the pattern under the target folder. + * @param {string} folder The absolute path to target folder. + * @param {string} filenamePattern The pattern to search for. + * @return {array} Return the array of relative file paths. + */ function findFiles(folder, filenamePattern) { const files = glob.sync(path.join('**', filenamePattern), { cwd: folder, @@ -24,6 +30,11 @@ function findFiles(folder, filenamePattern) { return files; } +/** + * Search for the windows sub-folder under the target folder. + * @param {string} folder The absolute path to the target folder. + * @return The absolute path to the windows folder, if it exists. + */ function findWindowsFolder(folder) { const winDir = 'windows'; const joinedDir = path.join(folder, winDir); @@ -34,6 +45,11 @@ function findWindowsFolder(folder) { return null; } +/** + * Checks if the target file path is a RNW solution file. + * @param {string} filePath The absolute file path to check. + * @return {boolean} Whether the path is to a RNW solution file. + */ function isRnwSolution(filePath) { return ( fs @@ -43,6 +59,11 @@ function isRnwSolution(filePath) { ); } +/** + * Search for the RNW solution files under the target folder. + * @param {string} winFolder The absolute path to target folder. + * @return {array} Return the array of relative file paths. + */ function findSolutionFiles(winFolder) { // First search for all potential solution files const allSolutions = findFiles(winFolder, '*.sln'); @@ -64,6 +85,11 @@ function findSolutionFiles(winFolder) { return solutionFiles; } +/** + * Checks if the target file path is a RNW lib project file. + * @param {string} filePath The absolute file path to check. + * @return {boolean} Whether the path is to a RNW lib project file. + */ function isRnwDependencyProject(filePath) { return ( fs @@ -73,6 +99,11 @@ function isRnwDependencyProject(filePath) { ); } +/** + * Search for the RNW lib project files under the target folder. + * @param {string} winFolder The absolute path to target folder. + * @return {array} Return the array of relative file paths. + */ function findDependencyProjectFiles(winFolder) { // First, search for all potential project files const allCppProj = findFiles(winFolder, '*.vcxproj'); @@ -97,6 +128,11 @@ function findDependencyProjectFiles(winFolder) { return dependencyProjectFiles; } +/** + * Checks if the target file path is a RNW app project file. + * @param {string} filePath The absolute file path to check. + * @return {boolean} Whether the path is to a RNW app project file. + */ function isRnwAppProject(filePath) { return ( fs @@ -106,6 +142,11 @@ function isRnwAppProject(filePath) { ); } +/** + * Search for the RNW app project files under the target folder. + * @param {string} winFolder The absolute path to target folder. + * @return {array} Return the array of relative file paths. + */ function findAppProjectFiles(winFolder) { // First, search for all potential project files const allCppProj = findFiles(winFolder, '*.vcxproj'); @@ -130,6 +171,11 @@ function findAppProjectFiles(winFolder) { return appProjectFiles; } +/** + * Returns the programming language of the project file. + * @param {string} projectPath The project file path to check. + * @return {string} The language string: cpp, cs, or null if unknown. + */ function getProjectLanguage(projectPath) { if (projectPath.endsWith('.vcxproj')) { return 'cpp'; @@ -139,10 +185,21 @@ function getProjectLanguage(projectPath) { return null; } +/** + * Reads in the contents of the target project file. + * @param {string} projectPath The target project file path. + * @return {string} The project file contents. + */ function readProjectFile(projectPath) { return fs.readFileSync(projectPath, 'utf8').toString(); } +/** + * Search for the given XML tag in the project contents and return its value. + * @param {string} projectContents The XML project contents. + * @param {string} tagName The XML tag to look for. + * @return {string} The value of the tag if it exists. + */ function findTagValue(projectContents, tagName) { const regexExpression = `<${tagName}>(.*)`; const regex = new RegExp(regexExpression, 'm'); @@ -151,6 +208,11 @@ function findTagValue(projectContents, tagName) { return match !== null && match.length === 2 ? match[1] : null; } +/** + * Gets the name of the project from the project contents. + * @param {string} projectContents The XML project contents. + * @return {string} The project name. + */ function getProjectName(projectContents) { return ( findTagValue(projectContents, 'ProjectName') || @@ -158,10 +220,20 @@ function getProjectName(projectContents) { ); } +/** + * Gets the namespace of the project from the project contents. + * @param {string} projectContents The XML project contents. + * @return {string} The project namespace. + */ function getProjectNamespace(projectContents) { return findTagValue(projectContents, 'RootNamespace'); } +/** + * Gets the guid of the project from the project contents. + * @param {string} projectContents The XML project contents. + * @return {string} The project guid. + */ function getProjectGuid(projectContents) { return findTagValue(projectContents, 'ProjectGuid'); } diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 450a9675349..1293a0d88e2 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -54,6 +54,12 @@ opt - Item is optional. If an override file exists, it MAY provide it. If no ov */ +/** + * Gets the config of any RNW native modules under the target folder. + * @param {string} folder The absolute path to the target folder. + * @param {object} userConfig A manually specified override config. + * @return {object} The config if any RNW native modules exist. + */ function dependencyConfigWindows(folder, userConfig = {}) { const usingManualProjectsOverride = 'projects' in userConfig; diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index 9f43b579fc7..fec4f722a99 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -37,6 +37,12 @@ opt - Item is optional. If an override file exists, it MAY provide it. If no ov */ +/** + * Gets the config of any RNW apps under the target folder. + * @param {string} folder The absolute path to the target folder. + * @param {object} userConfig A manually specified override config. + * @return {object} The config if any RNW apps exist. + */ function projectConfigWindows(folder, userConfig = {}) { const usingManualOverride = 'sourceDir' in userConfig; diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index e009c99a337..ae83bf2d10f 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -17,12 +17,23 @@ const generatorCommon = require('../../generator-common'); const templateRoot = path.join(__dirname, '../../generator-windows/templates'); +/** + * Logs the given message if verbose is True. + * @param {string} message The message to log. + * @param {boolean} verbose Whether or not verbose logging is enabled. + */ function verboseMessage(message, verbose) { if (verbose) { console.log(message); } } +/** + * Loads a source template file and performs the given replacements, normalizing CRLF. + * @param {string} srcFile Path to the source file. + * @param {object} replacements e.g. {'TextToBeReplaced': 'Replacement'} + * @return The contents of the file with the replacements applied. + */ function getNormalizedContents(srcFile, replacements) { // Template files are CRLF, JS-generated replacements are LF, normalize replacements to CRLF for (var key in replacements) { @@ -32,6 +43,14 @@ function getNormalizedContents(srcFile, replacements) { return generatorCommon.resolveContents(srcFile, replacements); } +/** + * Updates the target file with the expected contents if it's different. + * @param {string} filePath Path to the target file to update. + * @param {string} expectedContents The expected contents of the file. + * @param {boolean} verbose If true, enable verbose logging. + * @param {boolean} checkMode It true, don't make any changes. + * @return {boolean} Whether any changes were necessary. + */ function updateFile(filePath, expectedContents, verbose, checkMode) { const fileName = chalk.bold(path.basename(filePath)); verboseMessage(`Reading ${fileName}...`, verbose); @@ -57,6 +76,11 @@ function updateFile(filePath, expectedContents, verbose, checkMode) { return contentsChanged; } +/** + * Exits the script with the given status code. + * @param {number} statusCode The status code. + * @param {boolean} loggingWasEnabled Whether or not verbose lossing was enabled. + */ function exitProcessWithStatusCode(statusCode, loggingWasEnabled) { if (!loggingWasEnabled && statusCode !== 0) { console.log( @@ -68,6 +92,12 @@ function exitProcessWithStatusCode(statusCode, loggingWasEnabled) { process.exit(statusCode); } +/** + * Performs auto-linking for RNW native modules and apps. + * @param {object} config Config passed from react-native CLI. + * @param {object} args Args passed from react-native CLI. + * @param {object} options Options passed from react-native CLI. + */ async function updateAutoLink(config, args, options) { const startTime = performance.now(); diff --git a/vnext/local-cli/runWindows/utils/vstools.js b/vnext/local-cli/runWindows/utils/vstools.js index ff0b519c5f1..b59fb4a1a88 100644 --- a/vnext/local-cli/runWindows/utils/vstools.js +++ b/vnext/local-cli/runWindows/utils/vstools.js @@ -16,9 +16,9 @@ const projectTypeGuidsByLanguage = { /** * Checks is the given block of lines exists within an array of lines. - * @param {array} lines - The array of lines to search. - * @param {array} block - The block of lines to search for. - * @return {boolean} - True if the block of lines does exist within lines. + * @param {array} lines The array of lines to search. + * @param {array} block The block of lines to search for. + * @return {boolean} True if the block of lines does exist within lines. */ function linesContainsBlock(lines, block) { if (block.length > 0) { @@ -39,9 +39,9 @@ function linesContainsBlock(lines, block) { /** * Insert the given block of lines into an array of lines. - * @param {array} lines - The array of lines to insert into. - * @param {array} block - The block of lines to insert. - * @param {number} index - The index to perform the insert. + * @param {array} lines The array of lines to insert into. + * @param {array} block The block of lines to insert. + * @param {number} index The index to perform the insert. */ function insertBlockIntoLines(lines, block, index) { for (let i = 0; i < block.length; i++) { @@ -51,10 +51,10 @@ function insertBlockIntoLines(lines, block, index) { /** * Search through an array of lines for a block of lines starting with startLine and ending with endLine. - * @param {array} lines - The array of lines to search. - * @param {string} startLine - The first line of the block. - * @param {string} endLine - The last line of the block. - * @param {boolean} includeStartEnd - Include the start and end lines in the result. + * @param {array} lines The array of lines to search. + * @param {string} startLine The first line of the block. + * @param {string} endLine The last line of the block. + * @param {boolean} includeStartEnd Include the start and end lines in the result. * @return {array} The found block of lines, if found. */ function getBlockContentsFromLines( @@ -79,10 +79,10 @@ function getBlockContentsFromLines( /** * Adds the necessary info from a VS project into a VS solution file so that it will build. - * @param {string} slnFile - The Absolute path to the target VS solution file. - * @param {object} project - The object representing the project info. - * @param {boolean} verbose - If true, enable verbose logging. - * @param {boolean} checkMode - It true, don't make any changes. + * @param {string} slnFile The Absolute path to the target VS solution file. + * @param {object} project The object representing the project info. + * @param {boolean} verbose If true, enable verbose logging. + * @param {boolean} checkMode It true, don't make any changes. * @return {boolean} Whether any changes were necessary. */ function addProjectToSolution( From da80e91c0959fea2d8d09f438d291bf5e51f4296 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Fri, 5 Jun 2020 17:04:23 -0700 Subject: [PATCH 18/28] Fixing config schema for direct dependencies, adding examples of overrides --- vnext/local-cli/config/configUtils.js | 6 +- vnext/local-cli/config/dependencyConfig.js | 110 ++++++++++++--------- vnext/local-cli/config/projectConfig.js | 27 ++++- 3 files changed, 92 insertions(+), 51 deletions(-) diff --git a/vnext/local-cli/config/configUtils.js b/vnext/local-cli/config/configUtils.js index 15dd3bf1676..77fda435536 100644 --- a/vnext/local-cli/config/configUtils.js +++ b/vnext/local-cli/config/configUtils.js @@ -76,7 +76,7 @@ function findSolutionFiles(winFolder) { var solutionFiles = []; // Try to find any solution file that appears to be a react native solution - for (var solutionFile of allSolutions) { + for (const solutionFile of allSolutions) { if (isRnwSolution(path.join(winFolder, solutionFile))) { solutionFiles.push(solutionFile); } @@ -119,7 +119,7 @@ function findDependencyProjectFiles(winFolder) { var dependencyProjectFiles = []; // Try to find any project file that appears to be a dependency project - for (var projectFile of allProjects) { + for (const projectFile of allProjects) { if (isRnwDependencyProject(path.join(winFolder, projectFile))) { dependencyProjectFiles.push(projectFile); } @@ -162,7 +162,7 @@ function findAppProjectFiles(winFolder) { var appProjectFiles = []; // Try to find any project file that appears to be an app project - for (var projectFile of allProjects) { + for (const projectFile of allProjects) { if (isRnwAppProject(path.join(winFolder, projectFile))) { appProjectFiles.push(projectFile); } diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 1293a0d88e2..2c6b273bd9a 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -24,34 +24,53 @@ req - Item is required. If an override file exists, it MUST provide it. If no o opt - Item is optional. If an override file exists, it MAY provide it. If no override file exists, config may try to calculate it. { - folder: string, // (auto) Absolute path to the module root folder, determined by react-native config, ex: 'c:\path\to\app-name\node_modules\module-name' + folder: string, // (auto) Absolute path to the module root folder, determined by react-native config, ex: 'c:\path\to\app-name\node_modules\my-module' sourceDir: string, // (opt, req if projects defined) Relative path to the windows implementation under folder, ex: 'windows' - solutionFile: string, // (opt) Relative path to the module's VS solution file under sourceDir, ex: 'ModuleName.sln' + solutionFile: string, // (opt) Relative path to the module's VS solution file under sourceDir, ex: 'MyModule.sln' projects: [ // (opt) Array of VS projects that must be added to the consuming app's solution file, so they are built { - projectFile: string, // (req) Relative path to the VS project file under sourceDir, ex: 'ProjectName\ProjectName.vcxproj' for 'c:\path\to\app-name\node_modules\module-name\windows\ProjectName\ProjectName.vcxproj' - projectName: string, // (auto) Name of the project, determined from projectFile, ex: 'ProjectName' + projectFile: string, // (req) Relative path to the VS project file under sourceDir, ex: 'MyModule\MyModule.vcxproj' for 'c:\path\to\app-name\node_modules\my-module\windows\MyModule\MyModule.vcxproj' + directDependency: bool, // (req) Whether to add the project file as a dependency to the consuming app's project file. true for projects that provide native modules + projectName: string, // (auto) Name of the project, determined from projectFile, ex: 'MyModule' projectLang: string, // (auto) Language of the project, cpp or cs, determined from projectFile projectGuid: string, // (auto) Project identifier, determined from projectFile - directDependency: bool, // (req) Whether to add the project file as a dependency to the consuming app's project file. true for projects that provide native modules - cppHeaders: [], // (opt, but req if directDependency) Array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' - cppPackageProviders: [], // (opt, but req if directDependency) Array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' - csNamespaces: [], // (opt, but req if directDependency) Array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' - csPackageProviders: [], // (opt, but req if directDependency) Array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' + cppHeaders: [], // (opt) Array of cpp header include lines, ie: 'winrt/MyModule.h', to be transformed into '#include ' + cppPackageProviders: [], // (opt) Array of fully qualified cpp IReactPackageProviders, ie: 'MyModule::ReactPackageProvider' + csNamespaces: [], // (opt) Array of cs namespaces, ie: 'MyModule', to be transformed into 'using MyModule;' + csPackageProviders: [], // (opt) Array of fully qualified cs IReactPackageProviders, ie: 'MyModule.ReactPackageProvider' }, ], nugetPackages: [ // (opt) Array of nuget packages including native modules that must be added as a dependency to the consuming app. It can be empty, but by its nature it can't be calculated { packageName: string, // (req) Name of the nuget package to install packageVersion: string, // (req) Version of the nuget package to install - cppHeaders: [], // (req) Array of cpp header include lines, ie: 'winrt/ModuleName.h', to be transformed into '#include ' - cppPackageProviders: [], // (req) Array of fully qualified cpp IReactPackageProviders, ie: 'ModuleName::ReactPackageProvider' - csNamespaces: [], // (req) Array of cs namespaces, ie: 'ModuleName', to be transformed into 'using ModuleName;' - csPackageProviders: [], // (req) Array of fully qualified cs IReactPackageProviders, ie: 'ModuleName.ReactPackageProvider' + cppHeaders: [], // (req) Array of cpp header include lines, ie: 'winrt/NugetModule.h', to be transformed into '#include ' + cppPackageProviders: [], // (req) Array of fully qualified cpp IReactPackageProviders, ie: 'NugetModule::ReactPackageProvider' + csNamespaces: [], // (req) Array of cs namespaces, ie: 'NugetModule', to be transformed into 'using NugetModule;' + csPackageProviders: [], // (req) Array of fully qualified cs IReactPackageProviders, ie: 'NugetModule.ReactPackageProvider' }, ], } +Example react-native.config.js for a 'MyModule': + +module.exports = { + dependency: { + platforms: { + windows: { + sourceDir: 'windows', + solutionFile: 'MyModule.sln', + projects: [ + { + projectFile: 'MyModule\\MyModule.vcxproj', + directDependency: true, + } + ], + }, + }, + }, +}; + */ /** @@ -61,11 +80,13 @@ opt - Item is optional. If an override file exists, it MAY provide it. If no ov * @return {object} The config if any RNW native modules exist. */ function dependencyConfigWindows(folder, userConfig = {}) { - const usingManualProjectsOverride = 'projects' in userConfig; + const usingManualProjectsOverride = + 'projects' in userConfig && Array.isArray(userConfig.projects); var projects = usingManualProjectsOverride ? userConfig.projects : []; - const usingManualNugetPackagesOverride = 'nugetPackages' in userConfig; + const usingManualNugetPackagesOverride = + 'nugetPackages' in userConfig && Array.isArray(nugetPackages); var nugetPackages = usingManualNugetPackagesOverride ? userConfig.nugetPackages @@ -102,9 +123,9 @@ function dependencyConfigWindows(folder, userConfig = {}) { solutionFile = path.join(sourceDir, userConfig.solutionFile); } else if (!usingManualSolutionFile) { // No manually provided solutionFile, try to find it - const solutionFiles = configUtils.findSolutionFiled(sourceDir); - if (solutionFiles.length === 1) { - solutionFile = path.join(sourceDir, solutionFile); + const foundSolutions = configUtils.findSolutionFiles(sourceDir); + if (foundSolutions.length === 1) { + solutionFile = path.join(sourceDir, foundSolutions[0]); } } @@ -113,41 +134,42 @@ function dependencyConfigWindows(folder, userConfig = {}) { const alwaysRequired = ['projectFile', 'directDependency']; - const requiredForDirectDepenencies = [ - 'cppHeaders', - 'cppPackageProviders', - 'csNamespaces', - 'csPackageProviders', - ]; - - for (let i = 0; i < projects.length; i++) { + for (let project of projects) { // Verifying (req) items alwaysRequired.forEach(item => { - if (!(item in projects[i])) { + if (!(item in project)) { throw new Error( `${item} is required for each project in react-native.config`, ); } }); - const projectFile = path.join(sourceDir, projects[i].projectFile); + const projectFile = path.join(sourceDir, project.projectFile); const projectContents = configUtils.readProjectFile(projectFile); // Calculating (auto) items - projects[i].projectName = configUtils.getProjectName(projectContents); - projects[i].projectLang = configUtils.getProjectLanguage(projectFile); - projects[i].projectGuid = configUtils.getProjectGuid(projectContents); - - if (projects[i].directDependency) { - // Verifying (req) items - requiredForDirectDepenencies.forEach(item => { - if (!(item in projects[i])) { - throw new Error( - `${item} is required for each project in react-native.config`, - ); - } - }); + project.projectName = configUtils.getProjectName(projectContents); + project.projectLang = configUtils.getProjectLanguage(projectFile); + project.projectGuid = configUtils.getProjectGuid(projectContents); + + if (project.directDependency) { + // Calculating more (auto) items + + const projectNamespace = configUtils.getProjectNamespace( + projectContents, + ); + const cppNamespace = projectNamespace.replace(/\./g, '::'); + const csNamespace = projectNamespace.replace(/::/g, '.'); + + project.cppHeaders = project.cppHeaders || [`winrt/${csNamespace}.h`]; + project.cppPackageProviders = project.cppPackageProviders || [ + `${cppNamespace}::ReactPackageProvider`, + ]; + project.csNamespaces = project.csNamespaces || [`${csNamespace}`]; + project.csPackageProviders = project.csPackageProviders || [ + `${csNamespace}.ReactPackageProvider`, + ]; } } } else { @@ -155,8 +177,8 @@ function dependencyConfigWindows(folder, userConfig = {}) { const foundProjects = configUtils.findDependencyProjectFiles(sourceDir); - for (let i = 0; i < foundProjects.length; i++) { - const projectFile = path.join(sourceDir, foundProjects[i]); + for (const foundProject of foundProjects) { + const projectFile = path.join(sourceDir, foundProject); const projectLang = configUtils.getProjectLanguage(projectFile); @@ -194,7 +216,7 @@ function dependencyConfigWindows(folder, userConfig = {}) { return { folder, - sourceDir: sourceDir.substr(folder.length + 1), + sourceDir: sourceDir !== null ? sourceDir.substr(folder.length + 1) : null, solutionFile: solutionFile !== null ? solutionFile.substr(sourceDir.length + 1) : null, projects, diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index fec4f722a99..790efe9c8d3 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -24,17 +24,31 @@ req - Item is required. If an override file exists, it MUST provide it. If no o opt - Item is optional. If an override file exists, it MAY provide it. If no override file exists, config may try to calculate it. { - folder: string, // (auto) Absolute path to the app root folder, determined by react-native config, ex: 'c:\path\to\app-name' + folder: string, // (auto) Absolute path to the app root folder, determined by react-native config, ex: 'c:\path\to\my-app' sourceDir: string, // (req) Relative path to the windows implementation under folder, ex: 'windows' - solutionFile: string, // (req) Relative path to the app's VS solution file under sourceDir, ex: 'AppName.sln' + solutionFile: string, // (req) Relative path to the app's VS solution file under sourceDir, ex: 'MyApp.sln' project: { // (req) - projectFile: string, // (req) Relative path to the VS project file under sourceDir, ex: 'AppName\AppName.vcxproj' for 'c:\path\to\app-name\windows\AppName\AppName.vcxproj' - projectName: string, // (auto) Name of the project, determined from projectFile, ex: 'AppName' + projectFile: string, // (req) Relative path to the VS project file under sourceDir, ex: 'MyApp\MyApp.vcxproj' for 'c:\path\to\my-app\windows\MyApp\MyApp.vcxproj' + projectName: string, // (auto) Name of the project, determined from projectFile, ex: 'MyApp' projectLang: string, // (auto) Language of the project, cpp or cs, determined from projectFile projectGuid: string, // (auto) Project identifier, determined from projectFile }, } +Example react-native.config.js: + +module.exports = { + project: { + windows: { + sourceDir: 'windows', + solutionFile: 'MyApp.sln', + project: { + projectFile: 'MyApp\\MyApp.vcxproj', + }, + }, + }, +}; + */ /** @@ -99,6 +113,11 @@ function projectConfigWindows(folder, userConfig = {}) { var projectFile = null; if (usingManualProjectOverride) { // Manually provided project, so extract it + if (!('projectFile' in userConfig.project)) { + throw new Error( + 'projectFile is required for project in react-native.config', + ); + } projectFile = path.join(sourceDir, userConfig.project.projectFile); } else { // No manually provided project, try to find it From a4f4ee391ddfd590f13c644df9349e6599516292 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Fri, 5 Jun 2020 17:15:31 -0700 Subject: [PATCH 19/28] Fixed bug: a null userConfig should prevent auto-linking --- vnext/local-cli/config/dependencyConfig.js | 4 ++++ vnext/local-cli/config/projectConfig.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 2c6b273bd9a..463a5e7ac33 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -80,6 +80,10 @@ module.exports = { * @return {object} The config if any RNW native modules exist. */ function dependencyConfigWindows(folder, userConfig = {}) { + if (userConfig === null) { + return null; + } + const usingManualProjectsOverride = 'projects' in userConfig && Array.isArray(userConfig.projects); diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index 790efe9c8d3..daa1667894e 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -58,6 +58,10 @@ module.exports = { * @return {object} The config if any RNW apps exist. */ function projectConfigWindows(folder, userConfig = {}) { + if (userConfig === null) { + return null; + } + const usingManualOverride = 'sourceDir' in userConfig; const sourceDir = usingManualOverride From 81390ec63a67d0235e89081721428e8d406a4eee Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Mon, 8 Jun 2020 09:40:13 -0700 Subject: [PATCH 20/28] Addressing feedback: validate output from react-native config --- vnext/local-cli/runWindows/utils/autolink.js | 43 ++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index ae83bf2d10f..fd383953b9b 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -125,22 +125,59 @@ async function updateAutoLink(config, args, options) { verboseMessage('Parsing output...', verbose); const rnConfig = JSON.parse(output); - const windowsAppConfig = rnConfig.project.windows; - if (!windowsAppConfig) { + if (!('project' in rnConfig) || rnConfig.project === null) { + throw new Error('No app project in react-native config output'); + } + + const projectConfig = rnConfig.project; + + if (!('windows' in projectConfig) || projectConfig.windows === null) { throw new Error( - 'Windows auto-link only supported on windows app projects.', + 'Windows auto-link only supported on windows app projects', ); } verboseMessage('Found Windows app project, parsing...', verbose); + const windowsAppConfig = projectConfig.windows; + + const alwaysRequired = ['folder', 'sourceDir', 'solutionFile', 'project']; + + alwaysRequired.forEach(item => { + if (!(item in windowsAppConfig) || windowsAppConfig[item] === null) { + throw new Error( + `${item} is required but not specified by react-native config`, + ); + } + }); + const solutionFile = path.join( windowsAppConfig.folder, windowsAppConfig.sourceDir, windowsAppConfig.solutionFile, ); + const windowsAppProjectConfig = windowsAppConfig.project; + + const projectRequired = [ + 'projectFile', + 'projectName', + 'projectLang', + 'projectGuid', + ]; + + projectRequired.forEach(item => { + if ( + !(item in windowsAppProjectConfig) || + windowsAppProjectConfig[item] === null + ) { + throw new Error( + `project.${item} is required but not specified by react-native config`, + ); + } + }); + const projectFile = path.join( windowsAppConfig.folder, windowsAppConfig.sourceDir, From cedfce5e018d43b6aff04ba1a0025c45c56397e9 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Mon, 8 Jun 2020 16:44:37 -0700 Subject: [PATCH 21/28] Addressing feedback: consolidating solution searching --- vnext/local-cli/config/configUtils.js | 3 + vnext/local-cli/config/dependencyConfig.js | 69 ++++++---- vnext/local-cli/config/projectConfig.js | 136 ++++++++++--------- vnext/local-cli/runWindows/runWindows.js | 39 +++++- vnext/local-cli/runWindows/utils/autolink.js | 68 ++++++---- vnext/local-cli/runWindows/utils/build.js | 78 +++++++++-- 6 files changed, 255 insertions(+), 138 deletions(-) diff --git a/vnext/local-cli/config/configUtils.js b/vnext/local-cli/config/configUtils.js index 77fda435536..abb33c394cf 100644 --- a/vnext/local-cli/config/configUtils.js +++ b/vnext/local-cli/config/configUtils.js @@ -71,6 +71,9 @@ function findSolutionFiles(winFolder) { if (allSolutions.length === 0) { // If there're no solution files, return 0 return []; + } else if (allSolutions.length === 1) { + // If there is exactly one solution file, assume it's it + return [allSolutions[0]]; } var solutionFiles = []; diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 463a5e7ac33..7433f6786f7 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -87,24 +87,29 @@ function dependencyConfigWindows(folder, userConfig = {}) { const usingManualProjectsOverride = 'projects' in userConfig && Array.isArray(userConfig.projects); - var projects = usingManualProjectsOverride ? userConfig.projects : []; - const usingManualNugetPackagesOverride = - 'nugetPackages' in userConfig && Array.isArray(nugetPackages); + 'nugetPackages' in userConfig && Array.isArray(userConfig.nugetPackages); - var nugetPackages = usingManualNugetPackagesOverride - ? userConfig.nugetPackages - : []; + var result = { + folder, + projects: usingManualProjectsOverride ? userConfig.projects : [], + nugetPackages: usingManualNugetPackagesOverride + ? userConfig.nugetPackages + : [], + }; var sourceDir = null; - if (usingManualProjectsOverride && projects.length > 0) { + if (usingManualProjectsOverride && result.projects.length > 0) { // Manaully provided projects, so extract the sourceDir - if (!('sourceDir' in userConfig) || userConfig.sourceDir === null) { - throw new Error( - 'sourceDir is required if projects are specified, but it is not specified in react-native.config', - ); + if (!('sourceDir' in userConfig)) { + sourceDir = + 'Error: Source dir is required if projects are specified, but it is not specified in react-native.config.'; + } else if (userConfig.sourceDir === null) { + sourceDir = + 'Error: Source dir is required if projects are specified, but it is null in react-native.config.'; + } else { + sourceDir = path.join(folder, userConfig.sourceDir); } - sourceDir = path.join(folder, userConfig.sourceDir); } else if (!usingManualProjectsOverride) { // No manually provided projects, try to find sourceDir sourceDir = configUtils.findWindowsFolder(folder); @@ -112,13 +117,19 @@ function dependencyConfigWindows(folder, userConfig = {}) { if ( sourceDir === null && - projects.length === 0 && - nugetPackages.length === 0 + result.projects.length === 0 && + result.nugetPackages.length === 0 ) { - // Nothing to do here, bail + // Nothing to look for here, bail return null; + } else if (sourceDir !== null && sourceDir.startsWith('Error: ')) { + // Source dir error, bail with error + result.sourceDir = sourceDir; + return result; } + result.sourceDir = sourceDir.substr(folder.length + 1); + const usingManualSolutionFile = 'solutionFile' in userConfig; var solutionFile = null; @@ -133,21 +144,30 @@ function dependencyConfigWindows(folder, userConfig = {}) { } } + result.solutionFile = + solutionFile !== null ? solutionFile.substr(sourceDir.length + 1) : null; + if (usingManualProjectsOverride) { // react-native.config used, fill out (auto) items for each provided project, verify (req) items are present const alwaysRequired = ['projectFile', 'directDependency']; - for (let project of projects) { + for (let project of result.projects) { // Verifying (req) items + var errorFound = false; alwaysRequired.forEach(item => { if (!(item in project)) { - throw new Error( - `${item} is required for each project in react-native.config`, - ); + project[ + item + ] = `Error: ${item} is required for each project in react-native.config`; + errorFound = true; } }); + if (errorFound) { + break; + } + const projectFile = path.join(sourceDir, project.projectFile); const projectContents = configUtils.readProjectFile(projectFile); @@ -204,7 +224,7 @@ function dependencyConfigWindows(folder, userConfig = {}) { const csNamespaces = [`${csNamespace}`]; const csPackageProviders = [`${csNamespace}.ReactPackageProvider`]; - projects.push({ + result.projects.push({ projectFile: projectFile.substr(sourceDir.length + 1), projectName, projectLang, @@ -218,14 +238,7 @@ function dependencyConfigWindows(folder, userConfig = {}) { } } - return { - folder, - sourceDir: sourceDir !== null ? sourceDir.substr(folder.length + 1) : null, - solutionFile: - solutionFile !== null ? solutionFile.substr(sourceDir.length + 1) : null, - projects, - nugetPackages, - }; + return result; } module.exports = { diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index daa1667894e..22eae92f3e5 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -69,94 +69,96 @@ function projectConfigWindows(folder, userConfig = {}) { : configUtils.findWindowsFolder(folder); if (sourceDir === null) { + // Nothing to look for here, bail return null; } - const alwaysRequired = ['solutionFile', 'project']; + var result = { + folder: folder, + sourceDir: sourceDir.substr(folder.length + 1), + }; - if (usingManualOverride) { - // Verifying (req) items - alwaysRequired.forEach(item => { - if (!(item in userConfig)) { - throw new Error( - `${item} is required but not specified in react-native.config`, - ); - } - }); - } + var validProject = false; - const usingManualSolutionFile = 'solutionFile' in userConfig; + if (usingManualOverride) { + // Manual override, try to use it for solutionFile + if (!('solutionFile' in userConfig)) { + result.solutionFile = + 'Error: Solution file is required but not specified in react-native.config.'; + } else if (userConfig.solutionFile === null) { + result.solutionFile = + 'Error: Solution file is null in react-native.config.'; + } else { + result.solutionFile = userConfig.solutionFile; + } - var solutionFile = null; - if (usingManualSolutionFile && userConfig.solutionFile !== null) { - // Manually provided solutionFile, so extract it - solutionFile = path.join(sourceDir, userConfig.solutionFile); - } else if (!usingManualSolutionFile) { + // Manual override, try to use it for project + if (!('project' in userConfig)) { + result.project = + 'Error: Project is required but not specified in react-native.config.'; + } else if (userConfig.project === null) { + result.project = 'Error: Project is null in react-native.config.'; + } else { + if (!('projectFile' in userConfig.project)) { + result.project = { + projectFile: + 'Error: Project file is required for project in react-native.config.', + }; + } else if (userConfig.project.projectFile === null) { + result.project = { + projectFile: 'Error: Project file is null in react-native.config.', + }; + } else { + result.project = { + projectFile: userConfig.project.projectFile, + }; + validProject = true; + } + } + } else { // No manually provided solutionFile, try to find it const foundSolutions = configUtils.findSolutionFiles(sourceDir); if (foundSolutions.length === 0) { - throw new Error( - 'No app solution file found, please specify in react-native.config', - ); + result.solutionFile = + 'Error: No app solution file found, please specify in react-native.config.'; } else if (foundSolutions.length > 1) { - throw new Error( - 'Too many app solution files found, please specify in react-native.config', - ); + result.solutionFile = + 'Error: Too many app solution files found, please specify in react-native.config.'; + } else { + result.solutionFile = foundSolutions[0]; } - solutionFile = path.join(sourceDir, foundSolutions[0]); - } - if (solutionFile === null) { - throw new Error( - 'Unable to determine app solution file, please specify in react-native.config', - ); - } - - const usingManualProjectOverride = 'project' in userConfig; - - var projectFile = null; - if (usingManualProjectOverride) { - // Manually provided project, so extract it - if (!('projectFile' in userConfig.project)) { - throw new Error( - 'projectFile is required for project in react-native.config', - ); - } - projectFile = path.join(sourceDir, userConfig.project.projectFile); - } else { // No manually provided project, try to find it const foundProjects = configUtils.findAppProjectFiles(sourceDir); if (foundProjects.length === 0) { - throw new Error( - 'No app project file found, please specify in react-native.config', - ); + result.project = { + projectFile: + 'Error: No app project file found, please specify in react-native.config.', + }; } else if (foundProjects.length > 1) { - throw new Error( - 'Too many app project files found, please specify in react-native.config', - ); + result.project = { + projectFile: + 'Error: Too many app project files found, please specify in react-native.config.', + }; + } else { + result.project = { + projectFile: foundProjects[0], + }; + validProject = true; } - projectFile = path.join(sourceDir, foundProjects[0]); } - if (projectFile === null) { - throw new Error( - 'Unable to determine app project file, please specify in react-native.config', - ); - } + if (validProject) { + const projectFile = path.join(sourceDir, result.project.projectFile); + const projectContents = configUtils.readProjectFile(projectFile); - const projectContents = configUtils.readProjectFile(projectFile); + // Add missing (auto) items + result.project.projectName = configUtils.getProjectName(projectContents); + result.project.projectLang = configUtils.getProjectLanguage(projectFile); + result.project.projectGuid = configUtils.getProjectGuid(projectContents); + } - return { - folder, - sourceDir: sourceDir.substr(folder.length + 1), - solutionFile: solutionFile.substr(sourceDir.length + 1), - project: { - projectFile: projectFile.substr(sourceDir.length + 1), - projectName: configUtils.getProjectName(projectContents), - projectLang: configUtils.getProjectLanguage(projectFile), - projectGuid: configUtils.getProjectGuid(projectContents), - }, - }; + return result; } module.exports = { diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index 7ee5fb06d34..16e53deffab 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -15,6 +15,9 @@ const autolink = require('./utils/autolink'); const chalk = require('chalk'); +const findProjectRoot = require('@react-native-community/cli/build/tools/config/findProjectRoot') + .default; + function ExitProcessWithError(loggingWasEnabled) { if (!loggingWasEnabled) { console.log( @@ -24,7 +27,13 @@ function ExitProcessWithError(loggingWasEnabled) { process.exit(1); } -async function runWindows(config, args, options) { +/** + * Performs build deploy and launch of RNW apps. + * @param {array} args Unprocessed args passed from react-native CLI. + * @param {object} config Config passed from react-native CLI. + * @param {object} options Options passed from react-native CLI. + */ +async function runWindows(args, config, options) { const verbose = options.logging; if (verbose) { @@ -47,15 +56,23 @@ async function runWindows(config, args, options) { } } - // Fix up options - options.root = options.root || process.cwd(); - const slnFile = options.sln || build.getSolutionFile(options); + // Either use the specified root or get the default one + options.root = options.root || findProjectRoot(); + + // Get the solution file + const slnFile = build.getAppSolutionFile(options, config); if (options.autolink) { const autolinkConfig = config; const autolinkArgs = []; - const autoLinkOptions = {logging: options.logging}; + const autoLinkOptions = { + logging: options.logging, + proj: options.proj, + sln: options.sln, + }; await autolink.func(autolinkConfig, autolinkArgs, autoLinkOptions); + } else { + newInfo('Autolink step is skipped'); } if (options.build) { @@ -110,6 +127,13 @@ async function runWindows(config, args, options) { await deploy.startServerInNewWindow(options, verbose); if (options.deploy) { + if (!slnFile) { + newError( + 'Visual Studio Solution file not found. Maybe run "react-native windows" first?', + ); + ExitProcessWithError(options.logging); + } + try { if (options.device || options.emulator || options.target) { await deploy.deployToDevice(options, verbose); @@ -231,6 +255,11 @@ module.exports = { description: 'Solution file to build, e.g. windows\\myApp.sln', default: undefined, }, + { + command: '--proj [string]', + description: 'Project file to build, e.g. windows\\myApp\\myApp.vcxproj', + default: undefined, + }, { command: '--msbuildprops [string]', description: diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index fd383953b9b..a1de2e78a81 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -5,7 +5,6 @@ */ // @ts-check -const execSync = require('child_process').execSync; const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); @@ -94,11 +93,11 @@ function exitProcessWithStatusCode(statusCode, loggingWasEnabled) { /** * Performs auto-linking for RNW native modules and apps. + * @param {array} args Unprocessed args passed from react-native CLI. * @param {object} config Config passed from react-native CLI. - * @param {object} args Args passed from react-native CLI. * @param {object} options Options passed from react-native CLI. */ -async function updateAutoLink(config, args, options) { +async function updateAutoLink(args, config, options) { const startTime = performance.now(); const verbose = options.logging; @@ -113,24 +112,10 @@ async function updateAutoLink(config, args, options) { verboseMessage('', verbose); - const execString = 'react-native config'; - let output; try { - verboseMessage(`Running ${chalk.bold('react-native config')}...`, verbose); + verboseMessage('Parsing project...', verbose); - output = execSync(execString).toString(); - - verboseMessage(output, verbose); - - verboseMessage('Parsing output...', verbose); - - const rnConfig = JSON.parse(output); - - if (!('project' in rnConfig) || rnConfig.project === null) { - throw new Error('No app project in react-native config output'); - } - - const projectConfig = rnConfig.project; + const projectConfig = config.project; if (!('windows' in projectConfig) || projectConfig.windows === null) { throw new Error( @@ -138,10 +123,11 @@ async function updateAutoLink(config, args, options) { ); } - verboseMessage('Found Windows app project, parsing...', verbose); - const windowsAppConfig = projectConfig.windows; + verboseMessage('Found windows app project, config:', verbose); + verboseMessage(windowsAppConfig, verbose); + const alwaysRequired = ['folder', 'sourceDir', 'solutionFile', 'project']; alwaysRequired.forEach(item => { @@ -189,19 +175,49 @@ async function updateAutoLink(config, args, options) { verboseMessage('Parsing dependencies...', verbose); - const dependencies = rnConfig.dependencies; + const dependenciesConfig = config.dependencies; let windowsDependencies = {}; - for (const dependencyName in dependencies) { - const windowsDependency = dependencies[dependencyName].platforms.windows; + for (const dependencyName in dependenciesConfig) { + const windowsDependency = + dependenciesConfig[dependencyName].platforms.windows; if (windowsDependency) { verboseMessage( - `Found dependency ${chalk.bold(dependencyName)}.`, + `${chalk.bold(dependencyName)} has windows implementation, config:`, verbose, ); - windowsDependencies[dependencyName] = windowsDependency; + verboseMessage(windowsDependency, verbose); + + var dependencyIsValid = true; + + dependencyIsValid = + dependencyIsValid && + 'sourceDir' in windowsDependency && + windowsDependency.sourceDir !== null && + !windowsDependency.sourceDir.startsWith('Error: '); + + if ( + 'projects' in windowsDependency && + Array.isArray(windowsDependency.projects) + ) { + windowsDependency.projects.forEach(project => { + const itemsToCheck = ['projectFile', 'directDependency']; + itemsToCheck.forEach(item => { + dependencyIsValid = + dependencyIsValid && + item in project && + project[item] !== null && + !project[item].toString().startsWith('Error: '); + }); + }); + } + + if (dependencyIsValid) { + verboseMessage(`Adding ${chalk.bold(dependencyName)}.`, verbose); + windowsDependencies[dependencyName] = windowsDependency; + } } } diff --git a/vnext/local-cli/runWindows/utils/build.js b/vnext/local-cli/runWindows/utils/build.js index ff08341df4a..237320a6757 100644 --- a/vnext/local-cli/runWindows/utils/build.js +++ b/vnext/local-cli/runWindows/utils/build.js @@ -10,12 +10,16 @@ const fs = require('fs'); const os = require('os'); const path = require('path'); const {execSync} = require('child_process'); -const glob = require('glob'); + const MSBuildTools = require('./msbuildtools'); const Version = require('./version'); -const {commandWithProgress, newSpinner} = require('./commandWithProgress'); +const { + commandWithProgress, + newSpinner, + newError, +} = require('./commandWithProgress'); const util = require('util'); -const chalk = require('chalk'); + const existsAsync = util.promisify(fs.exists); async function buildSolution( @@ -120,17 +124,66 @@ async function restoreNuGetPackages(options, slnFile, verbose) { } } -function getSolutionFile(options) { - const solutions = glob.sync(path.join(options.root, 'windows/*.sln')); - if (solutions.length === 0) { +const configErrorString = 'Error: '; + +function getAppSolutionFile(options, config) { + // Use the solution file if specified + if (options.sln) { + return path.join(options.root, options.sln); + } + + // Check the answer from react-native config + const windowsAppConfig = config.project.windows; + const configSolutionFile = windowsAppConfig.solutionFile; + + if (configSolutionFile.startsWith(configErrorString)) { + newError( + configSolutionFile.substr(configErrorString.length) + + ' Optionally, use --sln {slnFile}.', + ); return null; - } else if (solutions.length === 1) { - return solutions[0]; } else { - console.log(chalk.red('More than one solution file found:')); - console.log(chalk.bold(solutions.map(x => fs.realpathSync(x)).join('\n'))); - console.log('Use --sln {slnFile} to specify which one to build'); + return path.join( + windowsAppConfig.folder, + windowsAppConfig.sourceDir, + configSolutionFile, + ); + } +} + +function getAppProjectFile(options, config) { + // Use the project file if specified + if (options.proj) { + return path.join(options.root, options.proj); + } + + // Check the answer from react-native config + const windowsAppConfig = config.project.windows; + const configProject = windowsAppConfig.project; + + if ( + typeof configProject === 'string' && + configProject.startsWith(configErrorString) + ) { + newError( + configProject.substr(configErrorString.length) + + ' Optionally, use --proj {projFile}.', + ); return null; + } else { + const configProjectFile = configProject.projectFile; + if (configProjectFile.startsWith(configErrorString)) { + newError( + configProjectFile.substr(configErrorString.length) + + ' Optionally, use --proj {projFile}.', + ); + return null; + } + return path.join( + windowsAppConfig.folder, + windowsAppConfig.sourceDir, + configProjectFile, + ); } } @@ -148,7 +201,8 @@ function parseMsBuildProps(options) { module.exports = { buildSolution, - getSolutionFile, + getAppSolutionFile, + getAppProjectFile, restoreNuGetPackages, parseMsBuildProps, }; From 9a631e97838190d2335647c63fcb8df670714f28 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Tue, 9 Jun 2020 13:40:05 -0700 Subject: [PATCH 22/28] Fixed build error in run-windows --- .../windows/ReactUWPTestApp/AutolinkedNativeModules.g.cs | 2 +- .../windows/SampleAppCPP/AutolinkedNativeModules.g.cpp | 2 +- .../windows/SampleAppCS/AutolinkedNativeModules.g.cs | 2 +- .../windows/playground-win32/AutolinkedNativeModules.g.cpp | 2 +- .../windows/playground/AutolinkedNativeModules.g.cpp | 2 +- vnext/local-cli/config/configUtils.js | 3 ++- vnext/local-cli/runWindows/runWindows.js | 4 ++-- 7 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/E2ETest/windows/ReactUWPTestApp/AutolinkedNativeModules.g.cs b/packages/E2ETest/windows/ReactUWPTestApp/AutolinkedNativeModules.g.cs index fd512bbb55e..72a24f0937d 100644 --- a/packages/E2ETest/windows/ReactUWPTestApp/AutolinkedNativeModules.g.cs +++ b/packages/E2ETest/windows/ReactUWPTestApp/AutolinkedNativeModules.g.cs @@ -7,7 +7,7 @@ namespace Microsoft.ReactNative.Managed internal static class AutolinkedNativeModules { internal static void RegisterAutolinkedNativeModulePackages(IList packageProviders) - { + { } } } diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp index e363d21605e..f3b94db59f9 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCPP/AutolinkedNativeModules.g.cpp @@ -7,7 +7,7 @@ namespace winrt::Microsoft::ReactNative { void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ +{ } } diff --git a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCS/AutolinkedNativeModules.g.cs b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCS/AutolinkedNativeModules.g.cs index fd512bbb55e..72a24f0937d 100644 --- a/packages/microsoft-reactnative-sampleapps/windows/SampleAppCS/AutolinkedNativeModules.g.cs +++ b/packages/microsoft-reactnative-sampleapps/windows/SampleAppCS/AutolinkedNativeModules.g.cs @@ -7,7 +7,7 @@ namespace Microsoft.ReactNative.Managed internal static class AutolinkedNativeModules { internal static void RegisterAutolinkedNativeModulePackages(IList packageProviders) - { + { } } } diff --git a/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp index e363d21605e..f3b94db59f9 100644 --- a/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp +++ b/packages/playground/windows/playground-win32/AutolinkedNativeModules.g.cpp @@ -7,7 +7,7 @@ namespace winrt::Microsoft::ReactNative { void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ +{ } } diff --git a/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp b/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp index e363d21605e..f3b94db59f9 100644 --- a/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp +++ b/packages/playground/windows/playground/AutolinkedNativeModules.g.cpp @@ -7,7 +7,7 @@ namespace winrt::Microsoft::ReactNative { void RegisterAutolinkedNativeModulePackages(winrt::Windows::Foundation::Collections::IVector const& packageProviders) -{ +{ } } diff --git a/vnext/local-cli/config/configUtils.js b/vnext/local-cli/config/configUtils.js index abb33c394cf..6cdb2e53589 100644 --- a/vnext/local-cli/config/configUtils.js +++ b/vnext/local-cli/config/configUtils.js @@ -219,7 +219,8 @@ function findTagValue(projectContents, tagName) { function getProjectName(projectContents) { return ( findTagValue(projectContents, 'ProjectName') || - findTagValue(projectContents, 'AssemblyName') + findTagValue(projectContents, 'AssemblyName') || + '' ); } diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index 8ebd70dfd71..c3037d10aab 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -63,14 +63,14 @@ async function runWindows(args, config, options) { const slnFile = build.getAppSolutionFile(options, config); if (options.autolink) { - const autolinkConfig = config; const autolinkArgs = []; + const autolinkConfig = config; const autoLinkOptions = { logging: options.logging, proj: options.proj, sln: options.sln, }; - await autolink.func(autolinkConfig, autolinkArgs, autoLinkOptions); + await autolink.func(autolinkArgs, autolinkConfig, autoLinkOptions); } else { newInfo('Autolink step is skipped'); } From f018c0ab68dfe30a18c2ab71d641db3f1bcaf7c6 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Tue, 9 Jun 2020 14:23:06 -0700 Subject: [PATCH 23/28] Fixed run-windows getting project root --- vnext/local-cli/runWindows/runWindows.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index c3037d10aab..0d3de85d54a 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -15,9 +15,6 @@ const autolink = require('./utils/autolink'); const chalk = require('chalk'); -const findProjectRoot = require('@react-native-community/cli/build/tools/config/findProjectRoot') - .default; - function ExitProcessWithError(loggingWasEnabled) { if (!loggingWasEnabled) { console.log( @@ -57,7 +54,7 @@ async function runWindows(args, config, options) { } // Either use the specified root or get the default one - options.root = options.root || findProjectRoot(); + options.root = options.root || config.root; // Get the solution file const slnFile = build.getAppSolutionFile(options, config); From df666e2c6e3ffb68fd59ec8be6a4cd0ca27fa891 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Tue, 9 Jun 2020 15:44:09 -0700 Subject: [PATCH 24/28] Pass through sln and proj flags to autolinking, process errors --- vnext/local-cli/runWindows/runWindows.js | 5 +- vnext/local-cli/runWindows/utils/autolink.js | 53 +++++++++++++++++++- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index 0d3de85d54a..c9210a52657 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -249,12 +249,13 @@ module.exports = { }, { command: '--sln [string]', - description: 'Solution file to build, e.g. windows\\myApp.sln', + description: 'App solution file to build, e.g. windows\\myApp.sln', default: undefined, }, { command: '--proj [string]', - description: 'Project file to build, e.g. windows\\myApp\\myApp.vcxproj', + description: + 'App project file to build, e.g. windows\\myApp\\myApp.vcxproj', default: undefined, }, { diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index 52380586584..7fd9ca28cf8 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -14,6 +14,8 @@ const {newSpinner} = require('./commandWithProgress'); const vstools = require('./vstools'); const generatorCommon = require('../../generator-common'); +const configUtils = require('../../config/configUtils'); + const templateRoot = path.join(__dirname, '../../generator-windows/templates'); /** @@ -125,7 +127,32 @@ async function updateAutoLink(args, config, options) { ); } - const windowsAppConfig = projectConfig.windows; + var windowsAppConfig = projectConfig.windows; + + if (options.sln) { + const slnFile = path.join(windowsAppConfig.folder, options.sln); + + windowsAppConfig.solutionFile = path.relative( + path.join(windowsAppConfig.folder, windowsAppConfig.sourceDir), + slnFile, + ); + } + + if (options.proj) { + const projFile = path.join(windowsAppConfig.folder, options.proj); + + const projectContents = configUtils.readProjectFile(projFile); + + windowsAppConfig.project = { + projectFile: path.relative( + path.join(windowsAppConfig.folder, windowsAppConfig.sourceDir), + projFile, + ), + projectName: configUtils.getProjectName(projectContents), + projectLang: configUtils.getProjectLanguage(projFile), + projectGuid: configUtils.getProjectGuid(projectContents), + }; + } verboseMessage('Found windows app project, config:', verbose); verboseMessage(windowsAppConfig, verbose); @@ -137,6 +164,11 @@ async function updateAutoLink(args, config, options) { throw new Error( `${item} is required but not specified by react-native config`, ); + } else if ( + typeof windowsAppConfig[item] === 'string' && + windowsAppConfig[item].startsWith('Error: ') + ) { + throw new Error(`${item} invalid. ${windowsAppConfig[item]}`); } }); @@ -163,6 +195,13 @@ async function updateAutoLink(args, config, options) { throw new Error( `project.${item} is required but not specified by react-native config`, ); + } else if ( + typeof windowsAppProjectConfig[item] === 'string' && + windowsAppProjectConfig[item].startsWith('Error: ') + ) { + throw new Error( + `project.${item} invalid. ${windowsAppProjectConfig[item]}`, + ); } }); @@ -452,5 +491,17 @@ module.exports = { description: 'Only check whether any autolinked files need to change', default: false, }, + { + command: '--sln [string]', + description: + 'App solution file to use for auto-linking, e.g. windows\\myApp.sln', + default: undefined, + }, + { + command: '--proj [string]', + description: + 'App project file to use for auto-linking, e.g. windows\\myApp\\myApp.vcxproj', + default: undefined, + }, ], }; From 46b5c33ef08c696aca772f457400e65a82f144ed Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 11 Jun 2020 12:25:14 -0700 Subject: [PATCH 25/28] Addressing feedback: proper xml parsing --- vnext/local-cli/config/configUtils.js | 110 +++++++++++++++------ vnext/local-cli/config/dependencyConfig.js | 2 +- vnext/local-cli/config/projectConfig.js | 4 +- vnext/package.json | 8 +- 4 files changed, 88 insertions(+), 36 deletions(-) diff --git a/vnext/local-cli/config/configUtils.js b/vnext/local-cli/config/configUtils.js index 6cdb2e53589..b82bff8dd18 100644 --- a/vnext/local-cli/config/configUtils.js +++ b/vnext/local-cli/config/configUtils.js @@ -9,6 +9,13 @@ const fs = require('fs'); const path = require('path'); const glob = require('glob'); +const xmldom = require('xmldom').DOMParser; +const xpath = require('xpath'); + +const msbuildSelect = xpath.useNamespaces({ + msbuild: 'http://schemas.microsoft.com/developer/msbuild/2003', +}); + /** * Search for files matching the pattern under the target folder. * @param {string} folder The absolute path to target folder. @@ -22,6 +29,7 @@ function findFiles(folder, filenamePattern) { 'node_modules/**', '**/Debug/**', '**/Release/**', + '**/WinUI3/**', '**/Generated Files/**', '**/packages/**', ], @@ -46,7 +54,7 @@ function findWindowsFolder(folder) { } /** - * Checks if the target file path is a RNW solution file. + * Checks if the target file path is a RNW solution file by checking if it contains the string "ReactNative". * @param {string} filePath The absolute file path to check. * @return {boolean} Whether the path is to a RNW solution file. */ @@ -94,12 +102,22 @@ function findSolutionFiles(winFolder) { * @return {boolean} Whether the path is to a RNW lib project file. */ function isRnwDependencyProject(filePath) { - return ( - fs - .readFileSync(filePath) - .toString() - .search(/Microsoft\.ReactNative\.Uwp\.(Cpp|CSharp)Lib\.targets/) > 0 - ); + const projectContents = readProjectFile(filePath); + + const projectLang = getProjectLanguage(filePath); + if (projectLang === 'cs') { + return importProjectExists( + projectContents, + 'Microsoft.ReactNative.Uwp.CSharpLib.targets', + ); + } else if (projectLang === 'cpp') { + return importProjectExists( + projectContents, + 'Microsoft.ReactNative.Uwp.CppLib.targets', + ); + } + + return false; } /** @@ -137,12 +155,22 @@ function findDependencyProjectFiles(winFolder) { * @return {boolean} Whether the path is to a RNW app project file. */ function isRnwAppProject(filePath) { - return ( - fs - .readFileSync(filePath) - .toString() - .search(/Microsoft\.ReactNative\.Uwp\.(Cpp|CSharp)App\.targets/) > 0 - ); + const projectContents = readProjectFile(filePath); + + const projectLang = getProjectLanguage(filePath); + if (projectLang === 'cs') { + return importProjectExists( + projectContents, + 'Microsoft.ReactNative.Uwp.CSharpApp.targets', + ); + } else if (projectLang === 'cpp') { + return importProjectExists( + projectContents, + 'Microsoft.ReactNative.Uwp.CppApp.targets', + ); + } + + return false; } /** @@ -191,55 +219,77 @@ function getProjectLanguage(projectPath) { /** * Reads in the contents of the target project file. * @param {string} projectPath The target project file path. - * @return {string} The project file contents. + * @return {object} The project file contents. */ function readProjectFile(projectPath) { - return fs.readFileSync(projectPath, 'utf8').toString(); + const projectContents = fs.readFileSync(projectPath, 'utf8').toString(); + return new xmldom().parseFromString(projectContents, 'application/xml'); } /** - * Search for the given XML tag in the project contents and return its value. - * @param {string} projectContents The XML project contents. - * @param {string} tagName The XML tag to look for. + * Search for the given property in the project contents and return its value. + * @param {object} projectContents The XML project contents. + * @param {string} propertyName The property to look for. * @return {string} The value of the tag if it exists. */ -function findTagValue(projectContents, tagName) { - const regexExpression = `<${tagName}>(.*)`; - const regex = new RegExp(regexExpression, 'm'); - const match = projectContents.match(regex); +function findPropertyValue(projectContents, propertyName) { + var nodes = msbuildSelect( + `//msbuild:PropertyGroup/msbuild:${propertyName}`, + projectContents, + ); + + if (nodes.length > 0) { + // Take the last one + return nodes[nodes.length - 1].textContent; + } + + return null; +} + +/** + * Search for the given import project in the project contents and return if it exists. + * @param {object} projectContents The XML project contents. + * @param {string} projectName The project to look for. + * @return {boolean} If the target exists. + */ +function importProjectExists(projectContents, projectName) { + var nodes = msbuildSelect( + `//msbuild:Import[contains(@Project,'${projectName}')]`, + projectContents, + ); - return match !== null && match.length === 2 ? match[1] : null; + return nodes.length > 0; } /** * Gets the name of the project from the project contents. - * @param {string} projectContents The XML project contents. + * @param {object} projectContents The XML project contents. * @return {string} The project name. */ function getProjectName(projectContents) { return ( - findTagValue(projectContents, 'ProjectName') || - findTagValue(projectContents, 'AssemblyName') || + findPropertyValue(projectContents, 'ProjectName') || + findPropertyValue(projectContents, 'AssemblyName') || '' ); } /** * Gets the namespace of the project from the project contents. - * @param {string} projectContents The XML project contents. + * @param {object} projectContents The XML project contents. * @return {string} The project namespace. */ function getProjectNamespace(projectContents) { - return findTagValue(projectContents, 'RootNamespace'); + return findPropertyValue(projectContents, 'RootNamespace'); } /** * Gets the guid of the project from the project contents. - * @param {string} projectContents The XML project contents. + * @param {object} projectContents The XML project contents. * @return {string} The project guid. */ function getProjectGuid(projectContents) { - return findTagValue(projectContents, 'ProjectGuid'); + return findPropertyValue(projectContents, 'ProjectGuid'); } module.exports = { diff --git a/vnext/local-cli/config/dependencyConfig.js b/vnext/local-cli/config/dependencyConfig.js index 7433f6786f7..81230ff5cd4 100644 --- a/vnext/local-cli/config/dependencyConfig.js +++ b/vnext/local-cli/config/dependencyConfig.js @@ -14,7 +14,7 @@ const configUtils = require('./configUtils.js'); react-native config will generate the following JSON for each native module dependency under node_modules that has a windows implementation, in order to support auto-linking. This is done heurestically, so if the result isn't quite correct, native module developers -can provide a manual override file: react-native.config. +can provide a manual override file: react-native.config.js. Schema for dependencies: diff --git a/vnext/local-cli/config/projectConfig.js b/vnext/local-cli/config/projectConfig.js index 22eae92f3e5..c1fea11cdd3 100644 --- a/vnext/local-cli/config/projectConfig.js +++ b/vnext/local-cli/config/projectConfig.js @@ -14,7 +14,7 @@ const configUtils = require('./configUtils.js'); react-native config will generate the following JSON for app projects that have a windows implementation, as a target for auto-linking. This is done heurestically, so if the result isn't quite correct, app developers can provide a manual override -file: react-native.config. +file: react-native.config.js. Schema for app projects: @@ -35,7 +35,7 @@ opt - Item is optional. If an override file exists, it MAY provide it. If no ov }, } -Example react-native.config.js: +Example react-native.config.js for a 'MyApp': module.exports = { project: { diff --git a/vnext/package.json b/vnext/package.json index 43feef6c6c7..14c5dcc5c41 100644 --- a/vnext/package.json +++ b/vnext/package.json @@ -36,7 +36,9 @@ "shelljs": "^0.7.8", "username": "^5.1.0", "uuid": "^3.3.2", - "xml-parser": "^1.2.1" + "xml-parser": "^1.2.1", + "xmldom": "^0.3.0", + "xpath": "^0.0.27" }, "devDependencies": { "@microsoft/api-documenter": "^7.3.8", @@ -54,9 +56,9 @@ "just-scripts": "^0.36.1", "prettier": "1.17.0", "react": "16.11.0", - "react-native-windows-codegen": "0.0.6", - "react-native-platform-override": "^0.0.4", "react-native": "0.62.2", + "react-native-platform-override": "^0.0.4", + "react-native-windows-codegen": "0.0.6", "typescript": "^3.8.3" }, "peerDependencies": { From 919cbdbca104639c8da098abd1e9ef26169b8817 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 11 Jun 2020 15:19:44 -0700 Subject: [PATCH 26/28] Update sln/proj args to better explain their behavior --- vnext/local-cli/runWindows/runWindows.js | 5 +++-- vnext/local-cli/runWindows/utils/autolink.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/vnext/local-cli/runWindows/runWindows.js b/vnext/local-cli/runWindows/runWindows.js index c9210a52657..c5684a2aaf7 100644 --- a/vnext/local-cli/runWindows/runWindows.js +++ b/vnext/local-cli/runWindows/runWindows.js @@ -249,13 +249,14 @@ module.exports = { }, { command: '--sln [string]', - description: 'App solution file to build, e.g. windows\\myApp.sln', + description: + "Override the app solution file determined by 'react-native config', e.g. windows\\myApp.sln", default: undefined, }, { command: '--proj [string]', description: - 'App project file to build, e.g. windows\\myApp\\myApp.vcxproj', + "Override the app project file determined by 'react-native config', e.g. windows\\myApp\\myApp.vcxproj", default: undefined, }, { diff --git a/vnext/local-cli/runWindows/utils/autolink.js b/vnext/local-cli/runWindows/utils/autolink.js index 7fd9ca28cf8..37f41b80966 100644 --- a/vnext/local-cli/runWindows/utils/autolink.js +++ b/vnext/local-cli/runWindows/utils/autolink.js @@ -494,13 +494,13 @@ module.exports = { { command: '--sln [string]', description: - 'App solution file to use for auto-linking, e.g. windows\\myApp.sln', + "Override the app solution file determined by 'react-native config', e.g. windows\\myApp.sln", default: undefined, }, { command: '--proj [string]', description: - 'App project file to use for auto-linking, e.g. windows\\myApp\\myApp.vcxproj', + "Override the app project file determined by 'react-native config', e.g. windows\\myApp\\myApp.vcxproj", default: undefined, }, ], From 1fe5ec9f41ecaed48dd1e41534941ff39a640bb8 Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Mon, 15 Jun 2020 11:47:06 -0700 Subject: [PATCH 27/28] Fix to make sure autolink check always runs on the correct sln/proj --- vnext/PropertySheets/Autolink.props | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vnext/PropertySheets/Autolink.props b/vnext/PropertySheets/Autolink.props index f92142705b9..719ba31315f 100644 --- a/vnext/PropertySheets/Autolink.props +++ b/vnext/PropertySheets/Autolink.props @@ -8,8 +8,9 @@ true npx react-native autolink-windows - --check $([MSBuild]::GetDirectoryNameOfFileAbove($(ProjectDir), 'package.json')) + --check --sln $([MSBuild]::MakeRelative($(AutolinkCommandWorkingDir), $(SolutionPath))) --proj $([MSBuild]::MakeRelative($(AutolinkCommandWorkingDir), $(ProjectPath))) + --check From e30305f0a1f634c8b6cb1a258aeb48f48cc7d13a Mon Sep 17 00:00:00 2001 From: "Jon Thysell (JAUNTY)" Date: Thu, 18 Jun 2020 19:59:28 -0700 Subject: [PATCH 28/28] yarn lock fix --- yarn.lock | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/yarn.lock b/yarn.lock index 0c7dcde7442..230be92c4b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14095,6 +14095,11 @@ xmldom@0.1.x, xmldom@^0.1.19, xmldom@^0.1.22, xmldom@^0.1.27: resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= +xmldom@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.3.0.tgz#e625457f4300b5df9c2e1ecb776147ece47f3e5a" + integrity sha512-z9s6k3wxE+aZHgXYxSTpGDo7BYOUfJsIRyoZiX6HTjwpwfS2wpQBQKa2fD+ShLyPkqDYo5ud7KitmLZ2Cd6r0g== + xpath@0.0.27, xpath@^0.0.27: version "0.0.27" resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92"