@@ -295,6 +295,30 @@ _startsWithArgument(const wchar_t *x, int xLen, const wchar_t *y, int yLen)
295295}
296296
297297
298+ // Unlike regular startsWith, this function requires that the following
299+ // character is either NULL (that is, the entire string matches) or is one of
300+ // the characters in 'separators'.
301+ bool
302+ _startsWithSeparated (const wchar_t * x ,int xLen ,const wchar_t * y ,int yLen ,const wchar_t * separators )
303+ {
304+ if (!x || !y ) {
305+ return false;
306+ }
307+ yLen = yLen < 0 ? (int )wcsnlen_s (y ,MAXLEN ) :yLen ;
308+ xLen = xLen < 0 ? (int )wcsnlen_s (x ,MAXLEN ) :xLen ;
309+ if (xLen < yLen ) {
310+ return false;
311+ }
312+ if (xLen == yLen ) {
313+ return 0 == _compare (x ,xLen ,y ,yLen );
314+ }
315+ return separators &&
316+ 0 == _compare (x ,yLen ,y ,yLen )&&
317+ wcschr (separators ,x [yLen ])!= NULL ;
318+ }
319+
320+
321+
298322/******************************************************************************\
299323 *** HELP TEXT ***
300324\******************************************************************************/
@@ -409,6 +433,9 @@ typedef struct {
409433bool listPaths ;
410434// if true, display help message before contiuning
411435bool help ;
436+ // if set, limits search to registry keys with the specified Company
437+ // This is intended for debugging and testing only
438+ const wchar_t * limitToCompany ;
412439// dynamically allocated buffers to free later
413440struct _SearchInfoBuffer * _buffer ;
414441}SearchInfo ;
@@ -485,6 +512,7 @@ dumpSearchInfo(SearchInfo *search)
485512DEBUG_BOOL (list );
486513DEBUG_BOOL (listPaths );
487514DEBUG_BOOL (help );
515+ DEBUG (limitToCompany );
488516#undef DEBUG_BOOL
489517#undef DEBUG_2
490518#undef DEBUG
@@ -1602,6 +1630,10 @@ registrySearch(const SearchInfo *search, EnvironmentInfo **result, HKEY root, in
16021630 }
16031631break ;
16041632 }
1633+ if (search -> limitToCompany && 0 != _compare (search -> limitToCompany ,-1 ,buffer ,cchBuffer )) {
1634+ debug (L"# Skipping %s due to PYLAUNCHER_LIMIT_TO_COMPANY\n" ,buffer );
1635+ continue ;
1636+ }
16051637HKEY subkey ;
16061638if (ERROR_SUCCESS == RegOpenKeyExW (root ,buffer ,0 ,KEY_READ ,& subkey )) {
16071639exitCode = _registrySearchTags (search ,result ,subkey ,sortKey ,buffer ,fallbackArch );
@@ -1880,6 +1912,11 @@ collectEnvironments(const SearchInfo *search, EnvironmentInfo **result)
18801912 }
18811913 }
18821914
1915+ if (search -> limitToCompany ) {
1916+ debug (L"# Skipping APPX search due to PYLAUNCHER_LIMIT_TO_COMPANY\n" );
1917+ return 0 ;
1918+ }
1919+
18831920for (struct AppxSearchInfo * info = APPX_SEARCH ;info -> familyName ;++ info ) {
18841921exitCode = appxSearch (search ,result ,info -> familyName ,info -> tag ,info -> sortKey );
18851922if (exitCode && exitCode != RC_NO_PYTHON ) {
@@ -2049,12 +2086,15 @@ _companyMatches(const SearchInfo *search, const EnvironmentInfo *env)
20492086
20502087
20512088bool
2052- _tagMatches (const SearchInfo * search ,const EnvironmentInfo * env )
2089+ _tagMatches (const SearchInfo * search ,const EnvironmentInfo * env , int searchTagLength )
20532090{
2054- if (!search -> tag || !search -> tagLength ) {
2091+ if (searchTagLength < 0 ) {
2092+ searchTagLength = search -> tagLength ;
2093+ }
2094+ if (!search -> tag || !searchTagLength ) {
20552095return true;
20562096 }
2057- return _startsWith (env -> tag ,-1 ,search -> tag ,search -> tagLength );
2097+ return _startsWithSeparated (env -> tag ,-1 ,search -> tag ,searchTagLength , L".-" );
20582098}
20592099
20602100
@@ -2091,7 +2131,7 @@ _selectEnvironment(const SearchInfo *search, EnvironmentInfo *env, EnvironmentIn
20912131 }
20922132
20932133if (!search -> oldStyleTag ) {
2094- if (_companyMatches (search ,env )&& _tagMatches (search ,env )) {
2134+ if (_companyMatches (search ,env )&& _tagMatches (search ,env , -1 )) {
20952135// Because of how our sort tree is set up, we will walk up the
20962136// "prev" side and implicitly select the "best" best. By
20972137// returning straight after a match, we skip the entire "next"
@@ -2116,7 +2156,7 @@ _selectEnvironment(const SearchInfo *search, EnvironmentInfo *env, EnvironmentIn
21162156 }
21172157 }
21182158
2119- if (_startsWith ( env -> tag , -1 , search -> tag ,tagLength )) {
2159+ if (_tagMatches ( search , env ,tagLength )) {
21202160if (exclude32Bit && _is32Bit (env )) {
21212161debug (L"# Excluding %s/%s because it looks like 32bit\n" ,env -> company ,env -> tag );
21222162 }else if (only32Bit && !_is32Bit (env )) {
@@ -2143,10 +2183,6 @@ selectEnvironment(const SearchInfo *search, EnvironmentInfo *root, EnvironmentIn
21432183* best = NULL ;
21442184return RC_NO_PYTHON_AT_ALL ;
21452185 }
2146- if (!root -> next && !root -> prev ) {
2147- * best = root ;
2148- return 0 ;
2149- }
21502186
21512187EnvironmentInfo * result = NULL ;
21522188int exitCode = _selectEnvironment (search ,root ,& result );
@@ -2556,6 +2592,17 @@ process(int argc, wchar_t ** argv)
25562592debug (L"argv0: %s\nversion: %S\n" ,argv [0 ],PY_VERSION );
25572593 }
25582594
2595+ DWORD len = GetEnvironmentVariableW (L"PYLAUNCHER_LIMIT_TO_COMPANY" ,NULL ,0 );
2596+ if (len > 1 ) {
2597+ wchar_t * limitToCompany = allocSearchInfoBuffer (& search ,len );
2598+ search .limitToCompany = limitToCompany ;
2599+ if (0 == GetEnvironmentVariableW (L"PYLAUNCHER_LIMIT_TO_COMPANY" ,limitToCompany ,len )) {
2600+ exitCode = RC_INTERNAL_ERROR ;
2601+ winerror (0 ,L"Failed to read PYLAUNCHER_LIMIT_TO_COMPANY variable" );
2602+ gotoabort ;
2603+ }
2604+ }
2605+
25592606search .originalCmdLine = GetCommandLineW ();
25602607
25612608exitCode = performSearch (& search ,& envs );